@viraui/react 2026.9.0 → 2026.10.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/catalog.json +9 -3
  2. package/dist/components/index.d.ts +1 -0
  3. package/dist/components/prose/prose.css +1 -1
  4. package/dist/components/prose/prose.d.ts +8 -0
  5. package/dist/components/prose/prose.js +2 -1
  6. package/dist/components/tabs/tabs-list.js +29 -23
  7. package/dist/components/tabs/tabs-size-context.d.ts +4 -0
  8. package/dist/components/tabs/tabs-size-context.js +7 -0
  9. package/dist/components/tabs/tabs-tab.js +21 -16
  10. package/dist/components/tabs/tabs.d.ts +7 -0
  11. package/dist/components/tabs/tabs.js +12 -8
  12. package/dist/components/text/text.css +2 -2
  13. package/dist/components/text/text.d.ts +9 -6
  14. package/dist/components/text/text.js +2 -2
  15. package/dist/components/title/title.css +2 -2
  16. package/dist/components/title/title.d.ts +9 -6
  17. package/dist/components/title/title.js +2 -2
  18. package/dist/components/toggle-group/toggle-group.d.ts +3 -1
  19. package/dist/components/toggle-text-button/index.d.ts +2 -0
  20. package/dist/components/toggle-text-button/toggle-text-button.d.ts +27 -0
  21. package/dist/components/toggle-text-button/toggle-text-button.js +24 -0
  22. package/dist/index.js +2 -1
  23. package/dist/specs/components/icon-button/props.xml +2 -1
  24. package/dist/specs/components/icon-button/usage.xml +2 -1
  25. package/dist/specs/components/prose/meta.xml +1 -1
  26. package/dist/specs/components/prose/patterns.xml +1 -1
  27. package/dist/specs/components/prose/props.xml +8 -2
  28. package/dist/specs/components/prose/usage.xml +2 -1
  29. package/dist/specs/components/tabs/props.xml +3 -0
  30. package/dist/specs/components/tabs/usage.xml +1 -0
  31. package/dist/specs/components/text/meta.xml +1 -1
  32. package/dist/specs/components/text/patterns.xml +11 -11
  33. package/dist/specs/components/text/props.xml +10 -7
  34. package/dist/specs/components/text/usage.xml +2 -2
  35. package/dist/specs/components/title/meta.xml +1 -1
  36. package/dist/specs/components/title/patterns.xml +8 -8
  37. package/dist/specs/components/title/props.xml +10 -7
  38. package/dist/specs/components/title/usage.xml +3 -3
  39. package/dist/specs/components/toggle-button/props.xml +2 -0
  40. package/dist/specs/components/toggle-button/usage.xml +2 -0
  41. package/dist/specs/components/toggle-group/patterns.xml +2 -1
  42. package/dist/specs/components/toggle-group/usage.xml +5 -5
  43. package/dist/specs/components/toggle-text-button/a11y.xml +8 -0
  44. package/dist/specs/components/toggle-text-button/meta.xml +19 -0
  45. package/dist/specs/components/toggle-text-button/patterns.xml +44 -0
  46. package/dist/specs/components/toggle-text-button/props.xml +58 -0
  47. package/dist/specs/components/toggle-text-button/usage.xml +13 -0
  48. package/dist/specs/index.xml +14 -4
  49. package/package.json +2 -2
@@ -6,10 +6,11 @@
6
6
  <do>Keep a reading column: wrap Prose in Stack `maxWidth` in **`ch`** sized for the **body Text `size`** (smaller type → narrower; `m` mid band; large body → wider — not one fixed band for every article); prefer `ch` over rem; edge-to-edge wall of text is not OK</do>
7
7
  <do>Use `render={&lt;article /&gt;}` (or another landmark) when the region is the document landmark; name it if the landmark needs a name</do>
8
8
  <do>Leave type on Title/Text; Title inside Prose uses `lineHeight=&quot;s&quot;`; body leading via Text `lineHeight` per block (`m` / `l` / `s`); override `--vui-prose-gap-*` on the Prose root only when the em triple must change</do>
9
+ <do>Default Prose `wrap` is `pretty` (inherits to children). Prefer `wrap=&quot;balance&quot;` only when short multi-line blocks in the region need even line lengths; single-line metrics use child Text/Title `whiteSpace=&quot;nowrap&quot;`</do>
9
10
  <dont>Using Stack `rowGap` as article rhythm — one gap cannot split heading→body vs body→body</dont>
10
11
  <dont>Expecting default Title/Text `span` hosts to join rhythm — they are not heading/block tags</dont>
11
12
  <dont>Setting `as` — replace the root with `render` only</dont>
12
13
  <dont>Setting `align` on every Title/Text inside Prose when one Prose `align` covers the region</dont>
13
14
  <dont>Setting a widget `role` on Prose, or painting font/color/list/link chrome in Prose CSS</dont>
14
- <dont>Wrapping a short UI blurb in Prose “for leading” — use Text/Title `lineHeight` / `maxWidth` / `balanced` instead</dont>
15
+ <dont>Wrapping a short UI blurb in Prose “for leading” — use Text/Title `lineHeight` / `maxWidth` / `wrap` instead</dont>
15
16
  </usage>
@@ -12,6 +12,9 @@
12
12
  <prop name="listAlignment" kind="react">
13
13
  <description>Horizontal alignment of tab list within root Stack.</description>
14
14
  </prop>
15
+ <prop name="size" kind="react">
16
+ <description>Shared visual size for tab items (`Button`) and the active indicator footprint. Values match `Button` size (`s` | `m` | `l`). Default `m`.</description>
17
+ </prop>
15
18
  <prop name="Tab.addon" kind="react">
16
19
  <description>Decorative addon rendered alongside the tab label. Omit glyph `size` / width / height unless expressly overriding the icon-library default.</description>
17
20
  </prop>
@@ -4,6 +4,7 @@
4
4
  <do>Login/signup (or similar) form swap: wrap Tabs in Surface; panels hold Stack with Textfield(s) + Button (+ optional Fieldset) — use Stack `rowGap` / `fullWidth` / `expandChildren` and Textfield `fullWidth`; invent 0 width/gap CSS</do>
5
5
  <do>Compound order: `Tabs.List` → `Tabs.Tab`(s), then `Tabs.Viewport` → `Tabs.Panel`(s) with matching `value`</do>
6
6
  <do>Fill list chrome with `listAlignment=&quot;stretch&quot;`; center/end via `listAlignment` on the root</do>
7
+ <do>Set shared tab + indicator scale with root `size` (`m` default; `s` / `l` match `Button`)</do>
7
8
  <do>Decorative `Tab` `addon` / `addonPosition` beside a visible text label — omit glyph `size` / width / height unless expressly overriding</do>
8
9
  <dont>Using addons as the only visible label for a tab</dont>
9
10
  <dont>Passing `size={16}` on every Tab `addon` icon by habit — prefer icon-library default</dont>
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <meta id="text" title="Text" category="typography" shared_contract="text">
3
- <summary>Theme-aware body-copy primitive with tone, family, sizing, and readable-measure props (`maxWidth` / `lineHeight` / `balanced`).</summary>
3
+ <summary>Theme-aware body-copy primitive with tone, family, sizing, and readable-measure props (`maxWidth` / `lineHeight` / `wrap`).</summary>
4
4
  <exports>
5
5
  <export name="Text" />
6
6
  </exports>
@@ -6,15 +6,15 @@
6
6
  <example title="Auth muted lead (readable measure)">
7
7
  <tsx><![CDATA[
8
8
  <Stack vPadding={[0, 'l']} rowGap="xl" maxWidth="70ch">
9
- <Title size="1" render={<h1 />} maxWidth="36ch" lineHeight="s" balanced>
9
+ <Title size="1" render={<h1 />} maxWidth="36ch" lineHeight="s" wrap="balance">
10
10
  Design systems that ship
11
11
  </Title>
12
- <Text size="2xl" tone="muted" maxWidth="68ch" balanced>
12
+ <Text size="2xl" tone="muted" maxWidth="68ch" wrap="balance">
13
13
  Login to create custom ViraUI themes, access prompts and more.
14
14
  </Text>
15
15
  </Stack>
16
16
  ]]></tsx>
17
- <notes>From `auth/login-view`. Cap ~10–12 words/line with `maxWidth` in `ch` scaled to size (`2xl` wider than `s`); `balanced` on short wrapping hero copy; `lineHeight="s"` on large Title. Prefer `ch` over rem. No CSS width/line-height.</notes>
17
+ <notes>From `auth/login-view`. Cap ~10–12 words/line with `maxWidth` in `ch` scaled to size (`2xl` wider than `s`); `wrap="balance"` on short even-rag hero copy (default `pretty` covers most body); `lineHeight="s"` on large Title. Prefer `ch` over rem. No CSS width/line-height.</notes>
18
18
  </example>
19
19
 
20
20
  <example title="Card section blurb">
@@ -23,12 +23,12 @@
23
23
  <Title size="6" render={<h2 />}>
24
24
  Payout Threshold
25
25
  </Title>
26
- <Text tone="muted" size="s" maxWidth="45ch" balanced>
26
+ <Text tone="muted" size="s" maxWidth="45ch">
27
27
  Set the minimum balance required before a payout is triggered.
28
28
  </Text>
29
29
  </Stack>
30
30
  ]]></tsx>
31
- <notes>From preview-grid `payout-threshold` (+ preferences / workspace-usage). Default pairing: Title + muted `size="s"` Text; short blurb prefers `maxWidth` in `ch` + `balanced`.</notes>
31
+ <notes>From preview-grid `payout-threshold` (+ preferences / workspace-usage). Default pairing: Title + muted `size="s"` Text; short blurb prefers `maxWidth` in `ch` (default `wrap="pretty"`).</notes>
32
32
  </example>
33
33
 
34
34
  <example title="Nested weight + tone">
@@ -52,16 +52,16 @@
52
52
  </Text>
53
53
  </Stack>
54
54
  ]]></tsx>
55
- <notes>From preview-grid `payout-threshold`. `family="mono"` + muted xs for metric chrome — single-line; no `balanced`.</notes>
55
+ <notes>From preview-grid `payout-threshold`. `family="mono"` + muted xs for metric chrome — single-line; prefer `whiteSpace="nowrap"` when overflow risk; do not use `wrap="nowrap"` for metrics.</notes>
56
56
  </example>
57
57
 
58
58
  <example title="Form success paragraph">
59
59
  <tsx><![CDATA[
60
- <Text render={<p />} maxWidth="60ch" balanced>
60
+ <Text render={<p />} maxWidth="60ch">
61
61
  Check your email — we sent a link to your@email.com
62
62
  </Text>
63
63
  ]]></tsx>
64
- <notes>From `auth/login-form` acknowledged state. `render={<p />}` for block semantics; short wrap prefers `balanced` + `ch` measure.</notes>
64
+ <notes>From `auth/login-form` acknowledged state. `render={<p />}` for block semantics; short wrap relies on default `pretty` + `ch` measure.</notes>
65
65
  </example>
66
66
 
67
67
  <example title="Prose link">
@@ -76,15 +76,15 @@
76
76
  <example title="Empty-state centered">
77
77
  <tsx><![CDATA[
78
78
  <Stack hAlign="center" rowGap="m" maxWidth="36ch">
79
- <Title align="center" size="5" render={<h3 />} balanced>
79
+ <Title align="center" size="5" render={<h3 />} wrap="balance">
80
80
  No Team Members
81
81
  </Title>
82
- <Text size="s" align="center" maxWidth="40ch" tone="muted" balanced>
82
+ <Text size="s" align="center" maxWidth="40ch" tone="muted" wrap="balance">
83
83
  Invite your team to collaborate on this project.
84
84
  </Text>
85
85
  </Stack>
86
86
  ]]></tsx>
87
- <notes>From preview-grid `empty-team`. Centered muted copy with `maxWidth` in `ch` + `balanced`; Stack owns column cap / centering.</notes>
87
+ <notes>From preview-grid `empty-team`. Centered muted copy with `maxWidth` in `ch` + `wrap="balance"` for even rag; Stack owns column cap / centering.</notes>
88
88
  </example>
89
89
 
90
90
  <example title="Claimable balance rows">
@@ -2,7 +2,7 @@
2
2
  <props surface="text">
3
3
  <when_to_use>
4
4
  <item>Body copy with semantic tone (including brand `primary`) and token-backed typography</item>
5
- <item>Readable UI measure (~10–12 words/line) via `maxWidth` / parent Stack `maxWidth` **scaled to `size`** (smaller → narrower), `lineHeight` matched to size, and `balanced` on short wrapping paragraphs</item>
5
+ <item>Readable UI measure (~10–12 words/line) via `maxWidth` / parent Stack `maxWidth` **scaled to `size`** (smaller → narrower), `lineHeight` matched to size, and `wrap` (default `pretty`; `balance` on short even-rag blurbs)</item>
6
6
  <item>Letter-spacing via `tracking` or text-transform via `transform` — prefer props over CSS on the Text root</item>
7
7
  <item>Helper, label, or inline copy inside other components</item>
8
8
  <item>Mono or body family text; size locked by default (`fluid` false → --font-scale-body-*); pass `fluid` for viewport clamp (also gated by theme `--effect-fluid-typography`)</item>
@@ -11,14 +11,11 @@
11
11
  <item>Headings or display typography — use Title</item>
12
12
  <item>Heading semantics without an appropriate render root</item>
13
13
  <item>Long-form heading-aware article rhythm — use Prose (keep a reading column)</item>
14
- <item>Inventing CSS `letter-spacing` / `text-transform` / `line-height` / `max-width` / `text-wrap: balance` / `width: 100%` when `tracking` / `transform` / `lineHeight` / `maxWidth` / `balanced` / parent Stack `fullWidth` cover the need</item>
14
+ <item>Inventing CSS `letter-spacing` / `text-transform` / `line-height` / `max-width` / `text-wrap` / `width: 100%` when `tracking` / `transform` / `lineHeight` / `maxWidth` / `wrap` / parent Stack `fullWidth` cover the need</item>
15
15
  </when_not_to_use>
16
16
  <prop name="align" kind="react">
17
17
  <description>Aligns text using logical alignment values. Omit so `text-align` inherits from the parent.</description>
18
18
  </prop>
19
- <prop name="balanced" kind="react">
20
- <description>Enables balanced wrapping for block-like copy. Prefer on short wrapping UI paragraphs (hero lead, empty state, card blurb); omit when single-line or intentional ragged.</description>
21
- </prop>
22
19
  <prop name="color" kind="react">
23
20
  <description>Applies a literal color escape hatch when semantic `tone` is not used.</description>
24
21
  </prop>
@@ -77,7 +74,10 @@
77
74
  <description>Selects a semantic weight keyword or a numeric weight (`font-weight` + `wght` axis).</description>
78
75
  </prop>
79
76
  <prop name="whiteSpace" kind="react">
80
- <description>Controls white-space preservation and wrapping behavior.</description>
77
+ <description>Controls white-space collapse/preserve (`white-space`). Prefer `whiteSpace=&quot;nowrap&quot;` for single-line metrics/chrome (also collapses spaces). For wrapping algorithm / even line lengths use `wrap` (`text-wrap`) — not this prop.</description>
78
+ </prop>
79
+ <prop name="wrap" kind="react">
80
+ <description>Controls CSS `text-wrap` (wrapping algorithm / wrap on-off). Default `pretty` for orphan-aware UI copy. Prefer `balance` on short multi-line heads/blurbs that need even line lengths. Use `wrap=&quot;nowrap&quot;` only when you need no wrap without changing white-space collapse; prefer `whiteSpace=&quot;nowrap&quot;` for metrics.</description>
81
81
  </prop>
82
82
  <prop name="align" kind="css">
83
83
  <description>Text alignment on the root when the `align` prop is set; omitted inherits parent `text-align`.</description>
@@ -131,6 +131,9 @@
131
131
  <description>Text transform on the root. Prefer the `transform` React prop; set this hook only when theming beyond the prop.</description>
132
132
  </prop>
133
133
  <prop name="white-space" kind="css">
134
- <description>White-space behavior on the root.</description>
134
+ <description>White-space behavior on the root (`--vui-text-white-space`). Prefer the `whiteSpace` React prop.</description>
135
+ </prop>
136
+ <prop name="wrap" kind="css">
137
+ <description>Text-wrap on the root (`--vui-text-wrap`). Prefer the `wrap` React prop; default `pretty`.</description>
135
138
  </prop>
136
139
  </props>
@@ -3,7 +3,7 @@
3
3
  <do>Body copy, helpers, and labels — semantic `tone` before literal `color` or raw theme vars</do>
4
4
  <do>Readable UI measure: aim ~10–12 words per line — `maxWidth` / Stack `maxWidth` **scales with `size`** (smaller type → narrower column; larger → wider); prefer **`ch`** in patterns/examples (not `rem`); never one fixed ch for every size; no full-bleed wrapping blurbs</do>
5
5
  <do>Match `lineHeight` to size/context (`m` default on root; `l` when open; `s` for dense chrome); omit `lineHeight`/`size` on nested Text so the outer Text owns type</do>
6
- <do>Prefer `balanced` on short wrapping paragraphs (hero lead, empty state, card blurb); omit when single-line or intentional ragged</do>
6
+ <do>Default `wrap` is `pretty` (orphan-aware). Prefer `wrap=&quot;balance&quot;` on short multi-line blurbs that need even line lengths; use `whiteSpace=&quot;nowrap&quot;` for single-line metrics (not `wrap=&quot;nowrap&quot;` unless you must keep space collapse unchanged)</do>
7
7
  <do>Omit `align` so copy inherits parent `text-align`; pass `align` only to override</do>
8
8
  <do>Card / settings section blurb: Stack `rowGap` + Title heading + muted `size=&quot;s&quot;` Text — no invent CSS gap</do>
9
9
  <do>Settings row: Stack `direction=&quot;row&quot;` + Text label + Switch/Checkbox (or nest Text inside Switch `label`) — Surface for chrome</do>
@@ -16,7 +16,7 @@
16
16
  <do>Long articles / docs / privacy walls → Prose (not Text alone for heading-aware rhythm); still keep a reading column</do>
17
17
  <dont>Using Text for headings — use Title (`render` for h1–h6)</dont>
18
18
  <dont>Putting `data-no-link-style` on Text-as-a when the default underline hover is wanted</dont>
19
- <dont>Inventing CSS `letter-spacing` / `text-transform` / `line-height` / `max-width` / `text-wrap: balance` / `width: 100%` / `paddingBlock*` / `paddingInline*` when `tracking` / `transform` / `lineHeight` / `maxWidth` / `balanced` / Stack `fullWidth` / `hPadding`/`vPadding` tuples cover it</dont>
19
+ <dont>Inventing CSS `letter-spacing` / `text-transform` / `line-height` / `max-width` / `text-wrap` / `width: 100%` / `paddingBlock*` / `paddingInline*` when `tracking` / `transform` / `lineHeight` / `maxWidth` / `wrap` / Stack `fullWidth` / `hPadding`/`vPadding` tuples cover it</dont>
20
20
  <dont>Setting body color with raw `--global-*` when `tone` covers the role</dont>
21
21
  <dont>Full-bleed marketing/UI paragraphs across a wide shell when a ~10–12-word measure fits</dont>
22
22
  <dont>Reusing a large-body shell (`~65ch`–`75ch` / wide rem) for small `size` copy — column width must track `size`</dont>
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <meta id="title" title="Title" category="typography" shared_contract="title">
3
- <summary>Theme-aware heading and display typography with readable-measure props (`maxWidth` / `lineHeight` / `balanced`).</summary>
3
+ <summary>Theme-aware heading and display typography with readable-measure props (`maxWidth` / `lineHeight` / `wrap`).</summary>
4
4
  <exports>
5
5
  <export name="Title" />
6
6
  </exports>
@@ -5,11 +5,11 @@
5
5
 
6
6
  <example title="Auth hero heading (readable measure)">
7
7
  <tsx><![CDATA[
8
- <Title size="1" render={<h1 />} maxWidth="36ch" lineHeight="s" balanced>
8
+ <Title size="1" render={<h1 />} maxWidth="36ch" lineHeight="s" wrap="balance">
9
9
  Ship real UI at the speed of thought.
10
10
  </Title>
11
11
  ]]></tsx>
12
- <notes>From `auth/login-view`. Pair `size` with `render={<h1 />}`; cap measure in `ch`; `lineHeight="s"` + `balanced` on wrapping display. Prefer `ch` over rem. (Live app nests Typewriter inside; see typewriter patterns.)</notes>
12
+ <notes>From `auth/login-view`. Pair `size` with `render={<h1 />}`; cap measure in `ch`; `lineHeight="s"` + `wrap="balance"` on short even-rag display (default `pretty` covers most titles). Prefer `ch` over rem. (Live app nests Typewriter inside; see typewriter patterns.)</notes>
13
13
  </example>
14
14
 
15
15
  <example title="Card section heading">
@@ -18,21 +18,21 @@
18
18
  <Title size="6" render={<h2 />}>
19
19
  Preferences
20
20
  </Title>
21
- <Text size="s" maxWidth="45ch" tone="muted" balanced>
21
+ <Text size="s" maxWidth="45ch" tone="muted">
22
22
  Manage your account settings and notifications.
23
23
  </Text>
24
24
  </Stack>
25
25
  ]]></tsx>
26
- <notes>From preview-grid `preferences` (+ payout-threshold, notifications, …). Dense card titles use `size="6"` + `h2`; blurb keeps `ch` measure + `balanced`.</notes>
26
+ <notes>From preview-grid `preferences` (+ payout-threshold, notifications, …). Dense card titles use `size="6"` + `h2`; blurb keeps `ch` measure (default `wrap="pretty"`).</notes>
27
27
  </example>
28
28
 
29
29
  <example title="Empty-state heading">
30
30
  <tsx><![CDATA[
31
- <Title align="center" size="5" render={<h3 />} balanced>
31
+ <Title align="center" size="5" render={<h3 />} wrap="balance">
32
32
  No Team Members
33
33
  </Title>
34
34
  ]]></tsx>
35
- <notes>From preview-grid `empty-team`. Centered mid-size heading; prefer `balanced` when the title wraps; pair with muted Text below.</notes>
35
+ <notes>From preview-grid `empty-team`. Centered mid-size heading; prefer `wrap="balance"` when the title wraps and needs even rag; pair with muted Text below.</notes>
36
36
  </example>
37
37
 
38
38
  <example title="Display metric">
@@ -46,7 +46,7 @@
46
46
  </Text>
47
47
  </Stack>
48
48
  ]]></tsx>
49
- <notes>From preview-grid `daily-activity-goal`. `size="display"` for hero numerals; `whiteSpace="nowrap"` keeps metric on one line — skip `balanced`.</notes>
49
+ <notes>From preview-grid `daily-activity-goal`. `size="display"` for hero numerals; `whiteSpace="nowrap"` keeps metric on one line — prefer that over `wrap="nowrap"`.</notes>
50
50
  </example>
51
51
 
52
52
  <example title="Balance display">
@@ -69,7 +69,7 @@
69
69
  <Title size="4" render={<h3 />}>
70
70
  Colors
71
71
  </Title>
72
- <Text size="s" tone="muted" maxWidth="48ch" balanced>
72
+ <Text size="s" tone="muted" maxWidth="48ch">
73
73
  Preview of theme voice, contrast, and color rhythm across accents.
74
74
  </Text>
75
75
  </Stack>
@@ -4,21 +4,18 @@
4
4
  <item>Headings and display text with title presets</item>
5
5
  <item>Semantic heading tags chosen explicitly through render</item>
6
6
  <item>Size and render paired together for most heading use cases</item>
7
- <item>Readable wrapping titles: `maxWidth` (~10–12 words/line, **scaled to `size`** — smaller → narrower), `lineHeight` (`s` on large wrapping / inside Prose), and `balanced` on short multi-line heads</item>
7
+ <item>Readable wrapping titles: `maxWidth` (~10–12 words/line, **scaled to `size`** — smaller → narrower), `lineHeight` (`s` on large wrapping / inside Prose), and `wrap` (default `pretty`; `balance` on short even-rag heads)</item>
8
8
  <item>Letter-spacing via `tracking` or text-transform via `transform` — prefer props over CSS on the Title root</item>
9
9
  <item>Fluid clamp sizing across title appearance presets (theme `--effect-fluid-typography` or per-instance `fluid`; off → fixed at --font-scale-display / h-* for that size)</item>
10
10
  </when_to_use>
11
11
  <when_not_to_use>
12
12
  <item>Body copy — use Text</item>
13
13
  <item>Body or mono family typography</item>
14
- <item>Inventing CSS `letter-spacing` / `text-transform` / `line-height` / `max-width` / `text-wrap: balance` / `width: 100%` when `tracking` / `transform` / `lineHeight` / `maxWidth` / `balanced` / parent Stack `fullWidth` cover the need</item>
14
+ <item>Inventing CSS `letter-spacing` / `text-transform` / `line-height` / `max-width` / `text-wrap` / `width: 100%` when `tracking` / `transform` / `lineHeight` / `maxWidth` / `wrap` / parent Stack `fullWidth` cover the need</item>
15
15
  </when_not_to_use>
16
16
  <prop name="align" kind="react">
17
17
  <description>Aligns text using logical alignment values. Omit so `text-align` inherits from the parent.</description>
18
18
  </prop>
19
- <prop name="balanced" kind="react">
20
- <description>Enables balanced wrapping for block-like titles. Prefer on short wrapping heads (hero, empty state, section); omit for single-line / `whiteSpace=&quot;nowrap&quot;` metrics.</description>
21
- </prop>
22
19
  <prop name="color" kind="react">
23
20
  <description>Applies a literal color escape hatch.</description>
24
21
  </prop>
@@ -71,7 +68,10 @@
71
68
  <description>Overrides the size preset weight (`font-weight` + `wght` axis).</description>
72
69
  </prop>
73
70
  <prop name="whiteSpace" kind="react">
74
- <description>Controls white-space preservation and wrapping behavior.</description>
71
+ <description>Controls white-space collapse/preserve (`white-space`). Prefer `whiteSpace=&quot;nowrap&quot;` for single-line metrics/chrome (also collapses spaces). For wrapping algorithm / even line lengths use `wrap` (`text-wrap`) — not this prop.</description>
72
+ </prop>
73
+ <prop name="wrap" kind="react">
74
+ <description>Controls CSS `text-wrap` (wrapping algorithm / wrap on-off). Default `pretty` for orphan-aware titles. Prefer `balance` on short multi-line heads that need even line lengths. Use `wrap=&quot;nowrap&quot;` only when you need no wrap without changing white-space collapse; prefer `whiteSpace=&quot;nowrap&quot;` for metrics.</description>
75
75
  </prop>
76
76
  <prop name="align" kind="css">
77
77
  <description>Title alignment on the root when the `align` prop is set; omitted inherits parent `text-align`.</description>
@@ -125,6 +125,9 @@
125
125
  <description>Text transform on the root. Prefer the `transform` React prop; set this hook only when theming beyond the prop.</description>
126
126
  </prop>
127
127
  <prop name="white-space" kind="css">
128
- <description>White-space behavior on the root.</description>
128
+ <description>White-space behavior on the root (`--vui-title-white-space`). Prefer the `whiteSpace` React prop.</description>
129
+ </prop>
130
+ <prop name="wrap" kind="css">
131
+ <description>Text-wrap on the root (`--vui-title-wrap`). Prefer the `wrap` React prop; default `pretty`.</description>
129
132
  </prop>
130
133
  </props>
@@ -2,7 +2,7 @@
2
2
  <usage surface="title">
3
3
  <do>Headings and display type — pair `size` (visual preset) with `render={&lt;h1 /&gt;}`…`h6` for outline semantics (default root is `span`)</do>
4
4
  <do>Readable measure on wrapping titles: aim ~10–12 words per line — `maxWidth` / Stack `maxWidth` **scales with `size`** (smaller → narrower; larger → wider); prefer **`ch`** (not `rem`) in patterns/examples; large wrapping presets often `lineHeight=&quot;s&quot;`</do>
5
- <do>Prefer `balanced` on short wrapping titles (hero, empty state, section head); omit when single-line / `whiteSpace=&quot;nowrap&quot;` metrics</do>
5
+ <do>Default `wrap` is `pretty`. Prefer `wrap=&quot;balance&quot;` on short multi-line heads that need even line lengths; use `whiteSpace=&quot;nowrap&quot;` for single-line metrics (not `wrap=&quot;nowrap&quot;` unless space collapse must stay unchanged)</do>
6
6
  <do>Omit `align` so the heading inherits parent `text-align`; pass `align` only to override</do>
7
7
  <do>Title inside Prose → `lineHeight=&quot;s&quot;`; long-form rhythm stays on Prose — not Stack `rowGap`</do>
8
8
  <do>Card / panel header: Stack `rowGap=&quot;xs&quot;` + Title + muted Text blurb — Surface for card chrome</do>
@@ -14,7 +14,7 @@
14
14
  <do>Title-as-prose-link (`render={&lt;a /&gt;}`) keeps preflight link chrome — omit `data-no-link-style`</do>
15
15
  <dont>Assuming `size` changes the HTML tag — choose `render` explicitly</dont>
16
16
  <dont>Using Title for long body paragraphs — use Text</dont>
17
- <dont>Inventing CSS `letter-spacing` / `text-transform` / `line-height` / `max-width` / `text-wrap: balance` / `width: 100%` / `paddingBlock*` / `paddingInline*` when props / Stack / `hPadding`/`vPadding` tuples cover it</dont>
18
- <dont>Full-bleed wrapping headlines across a wide shell when a capped measure + `balanced` fits</dont>
17
+ <dont>Inventing CSS `letter-spacing` / `text-transform` / `line-height` / `max-width` / `text-wrap` / `width: 100%` / `paddingBlock*` / `paddingInline*` when props / Stack / `hPadding`/`vPadding` tuples cover it</dont>
18
+ <dont>Full-bleed wrapping headlines across a wide shell when a capped measure + `wrap` fits</dont>
19
19
  <dont>One fixed wide measure for every Title `size` — smaller presets need a narrower column for ~10–12 words/line</dont>
20
20
  </usage>
@@ -7,9 +7,11 @@
7
7
  <item>Temporally swap icons after user interactions with temporal pressed state, like copy feedback.</item>
8
8
  </when_to_use>
9
9
  <when_not_to_use>
10
+ <item>Labeled / visible-text toggles — use ToggleTextButton</item>
10
11
  <item>Settings-style on/off fields — use Switch</item>
11
12
  <item>One-shot actions with no pressed state — use IconButton</item>
12
13
  </when_not_to_use>
14
+
13
15
  <prop name="interpolateIcons" kind="react">
14
16
  <description>When `pressedIcon` is set, crossfade and rotate between resting and pressed icons (`data-interpolate=&quot;true&quot;`). When `false`, swap instantly with no transition.</description>
15
17
  </prop>
@@ -7,6 +7,7 @@
7
7
  <do>Temporal pressed feedback (e.g. copy) with optional `interpolateIcons` crossfade</do>
8
8
  <do>Segmented toolbars: nest ToggleButtons with `value` inside ToggleGroup; label each control and the group</do>
9
9
  <do>Optional chrome: wrap with Tooltip.Trigger / Badge — keep the accessible name on the ToggleButton</do>
10
+ <dont>Labeled / visible-text toggles — use ToggleTextButton (do not stuff text into the icon-only API)</dont>
10
11
  <dont>Using IconButton for persistent or temporal pressed toolbar state</dont>
11
12
  <dont>Using Switch for compact icon-only toolbar toggles</dont>
12
13
  <dont>Passing `icon` or `render` — ToggleButton owns icon swap and Toggle render wiring; use `restingIcon` / `pressedIcon`</dont>
@@ -14,3 +15,4 @@
14
15
  <dont>Omitting `value` on ToggleButton children inside a ToggleGroup</dont>
15
16
  <dont>Omitting both `aria-label` and `aria-labelledby`</dont>
16
17
  </usage>
18
+
@@ -1,6 +1,7 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <patterns surface="toggle-group">
3
- <!-- Sources: viraui-app — cleaned (no handlers, no i18n, no theme store). Compound: ToggleGroup + ToggleButton children with value. -->
3
+ <!-- Sources: viraui-app — cleaned (no handlers, no i18n, no theme store). Compound: ToggleGroup + ToggleButton and/or ToggleTextButton children with value. -->
4
+
4
5
 
5
6
  <example title="Mode switcher chrome">
6
7
  <tsx><![CDATA[
@@ -1,13 +1,13 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <usage surface="toggle-group">
3
- <do>Segmented icon toolbars such as text alignment, formatting, or mode switchers</do>
4
- <do>Single- or multi-select pressed states across related ToggleButton items (`value` / `defaultValue` as `string[]`)</do>
5
- <do>Compose only ToggleButton children — each child needs a `value` plus `aria-label` / `aria-labelledby`</do>
3
+ <do>Segmented toolbars such as text alignment, formatting, or mode switchers</do>
4
+ <do>Single- or multi-select pressed states across related ToggleButton and/or ToggleTextButton items (`value` / `defaultValue` as `string[]`)</do>
5
+ <do>Compose ToggleButton and/or ToggleTextButton children — each child needs a `value`; icon-only children also need `aria-label` / `aria-labelledby`</do>
6
6
  <do>Label the group root with `aria-label` or `aria-labelledby`</do>
7
7
  <do>Optional Surface chrome around the group for bordered toolbar shells — Stack gap is owned by ToggleGroup</do>
8
- <dont>Omitting `value` on ToggleButton children inside a group</dont>
8
+ <dont>Omitting `value` on ToggleButton / ToggleTextButton children inside a group</dont>
9
9
  <dont>Using Switch for icon-only toolbar toggle sets</dont>
10
10
  <dont>Using ToggleGroup for tabbed panel navigation — use Tabs</dont>
11
- <dont>Using ToggleGroup for a lone one-off toggle — use ToggleButton alone</dont>
11
+ <dont>Using ToggleGroup for a lone one-off toggle — use ToggleButton or ToggleTextButton alone</dont>
12
12
  <dont>Embedding Surface chrome inside the component — wrap demos or app chrome at the consumer</dont>
13
13
  </usage>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <a11y surface="toggle-text-button">
3
+ <rule>Prefer accessible name from visible text children</rule>
4
+ <rule>Empty or whitespace-only children require aria-label or aria-labelledby</rule>
5
+ <rule>Base UI Toggle exposes aria-pressed on the rendered button</rule>
6
+ <rule>Addon is decorative (aria-hidden) unless it is the sole content path — prefer text label; icon-only → ToggleButton</rule>
7
+ <rule>Keep the accessible name stable when loading state changes</rule>
8
+ </a11y>
@@ -0,0 +1,19 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <meta id="toggle-text-button" title="ToggleTextButton" category="action" shared_contract="button">
3
+ <summary>Labeled two-state toggle on Base UI Toggle with Button chrome; visible text children, optional addon / pressedVariant; no resting/pressed icon pair.</summary>
4
+ <exports>
5
+ <export name="ToggleTextButton" />
6
+ </exports>
7
+ <related>
8
+ <ref id="button" />
9
+ <ref id="toggle-button" />
10
+ <ref id="toggle-group" />
11
+ </related>
12
+ <units>
13
+ <unit kind="a11y" href="a11y.xml" />
14
+ <unit kind="props" href="props.xml" />
15
+ <unit kind="usage" href="usage.xml" />
16
+ <unit kind="patterns" href="patterns.xml" />
17
+ </units>
18
+ <base_ui href="https://base-ui.com/react/components/toggle#api-reference" />
19
+ </meta>
@@ -0,0 +1,44 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <patterns surface="toggle-text-button">
3
+ <example title="Labeled press">
4
+ <tsx><![CDATA[
5
+ <ToggleTextButton variant="flat" pressedVariant="primary">
6
+ Bold
7
+ </ToggleTextButton>
8
+ ]]></tsx>
9
+ <notes>Visible text supplies the accessible name. Default resting `variant` is `flat`; set `pressedVariant` for pressed chrome.</notes>
10
+ </example>
11
+
12
+ <example title="Addon + label">
13
+ <tsx><![CDATA[
14
+ <ToggleTextButton
15
+ addon={<Bell aria-hidden />}
16
+ addonPosition="start"
17
+ pressedVariant="secondary"
18
+ >
19
+ Notify
20
+ </ToggleTextButton>
21
+ ]]></tsx>
22
+ <notes>Button addon is decorative; keep the text label for naming. Omit glyph size props — Button CSS sizes via `--vui-button-icon-size`.</notes>
23
+ </example>
24
+
25
+ <example title="Mixed ToggleGroup">
26
+ <tsx><![CDATA[
27
+ <ToggleGroup aria-label="Editor options" defaultValue={['bold']}>
28
+ <ToggleTextButton value="bold" pressedVariant="secondary">
29
+ Bold
30
+ </ToggleTextButton>
31
+ <ToggleTextButton value="notify" addon={<Bell aria-hidden />} pressedVariant="secondary">
32
+ Notify
33
+ </ToggleTextButton>
34
+ <ToggleButton
35
+ value="lock"
36
+ aria-label="Lock"
37
+ restingIcon={<Lock aria-hidden />}
38
+ pressedVariant="secondary"
39
+ />
40
+ </ToggleGroup>
41
+ ]]></tsx>
42
+ <notes>ToggleGroup children may be ToggleTextButton and/or ToggleButton; each needs `value`. Icon-only siblings still need `aria-label`.</notes>
43
+ </example>
44
+ </patterns>
@@ -0,0 +1,58 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <props surface="toggle-text-button">
3
+ <when_to_use>
4
+ <item>Labeled toolbar or settings toggles with visible text</item>
5
+ <item>Two-state actions that share Button size, variant, addon, loading, and pill chrome</item>
6
+ <item>ToggleGroup segments that need a text label instead of an icon-only control</item>
7
+ </when_to_use>
8
+ <when_not_to_use>
9
+ <item>Icon-only two-state controls — use ToggleButton</item>
10
+ <item>Settings-style on/off fields without button chrome — use Switch</item>
11
+ <item>One-shot actions with no pressed state — use Button</item>
12
+ </when_not_to_use>
13
+ <prop name="addon" kind="react">
14
+ <description>Decorative addon rendered alongside the button label. Pass any React element; use a named icon from `@viraui/icons/react` or `&lt;Chip /&gt;` for status chips. Omit glyph `size` / width / height — Button CSS sizes SVG via `--vui-button-icon-size`.</description>
15
+ </prop>
16
+ <prop name="addonPosition" kind="react">
17
+ <description>Controls whether the addon appears before or after the label.</description>
18
+ </prop>
19
+ <prop name="loading" kind="react">
20
+ <description>Shows a centered spinner overlay while preserving the original content width.</description>
21
+ </prop>
22
+ <prop name="pill" kind="react">
23
+ <description>Applies a fully rounded pill shape to the button.</description>
24
+ </prop>
25
+ <prop name="pressedVariant" kind="react">
26
+ <description>Visual variant while the toggle is pressed.</description>
27
+ </prop>
28
+ <prop name="size" kind="react">
29
+ <description>Controls the visual button size.</description>
30
+ </prop>
31
+ <prop name="variant" kind="react">
32
+ <description>Visual variant while the toggle is not pressed.</description>
33
+ </prop>
34
+ <prop name="background" kind="css">
35
+ <description>Button fill; variant and state selectors preset this.</description>
36
+ </prop>
37
+ <prop name="color" kind="css">
38
+ <description>Button label and icon color.</description>
39
+ </prop>
40
+ <prop name="height" kind="css">
41
+ <description>Button block size; size prop presets via data attributes.</description>
42
+ </prop>
43
+ <prop name="icon-size" kind="css">
44
+ <description>Addon icon size inside the button (`--vui-button-icon-size`). Prefer this over setting `size` on the glyph ReactNode.</description>
45
+ </prop>
46
+ <prop name="padding-block" kind="css">
47
+ <description>Vertical padding inside the button.</description>
48
+ </prop>
49
+ <prop name="padding-inline" kind="css">
50
+ <description>Horizontal padding inside the button.</description>
51
+ </prop>
52
+ <prop name="radius" kind="css">
53
+ <description>Corner radius; pill sets full radius via data-pill.</description>
54
+ </prop>
55
+ <prop name="spinner-color" kind="css">
56
+ <description>Loading spinner color on the root.</description>
57
+ </prop>
58
+ </props>
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <usage surface="toggle-text-button">
3
+ <do>Labeled toolbar, filter, or settings toggles with visible text children</do>
4
+ <do>Optional Button `addon` / `addonPosition` / `size` / `loading` / `pill` chrome beside the label</do>
5
+ <do>Resting `variant` default `flat`; optional `pressedVariant` when pressed</do>
6
+ <do>Segmented toolbars: nest ToggleTextButton (and/or ToggleButton) with `value` inside ToggleGroup; label the group</do>
7
+ <do>Omit glyph `size` / width / height on `addon` — host CSS sizes SVG via `--vui-button-icon-size`</do>
8
+ <dont>Icon-only two-state controls — use ToggleButton with `restingIcon` / `pressedIcon`</dont>
9
+ <dont>Passing `restingIcon`, `pressedIcon`, `interpolateIcons`, or `render` — not part of this API</dont>
10
+ <dont>Omitting `value` on ToggleTextButton children inside a ToggleGroup</dont>
11
+ <dont>Empty/whitespace children without `aria-label` or `aria-labelledby`</dont>
12
+ <dont>Using Switch when Button chrome and pressed variant are the desired pattern</dont>
13
+ </usage>
@@ -1,5 +1,5 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <index package="@viraui/react" version="2026.9.0" generated="2026-10-15T06:47:40.657Z">
2
+ <index package="@viraui/react" version="2026.10.0-next.0" generated="2026-10-16T09:58:59.523Z">
3
3
  <categories>
4
4
  <category id="layout" />
5
5
  <category id="action" />
@@ -312,7 +312,7 @@
312
312
  </units>
313
313
  </entry>
314
314
  <entry id="prose" category="typography" title="Prose">
315
- <summary>Text-wall grouping region (articles, docs, privacy) with heading-aware em rhythm; reading column via parent Stack `maxWidth` sized for body Text `size`.</summary>
315
+ <summary>Text-wall grouping region (articles, docs, privacy) with heading-aware em rhythm, region `wrap` (default `pretty`), and reading column via parent Stack `maxWidth` sized for body Text `size`.</summary>
316
316
  <units>
317
317
  <unit kind="meta" href="components/prose/meta.xml" />
318
318
  <unit kind="a11y" href="components/prose/a11y.xml" />
@@ -442,7 +442,7 @@
442
442
  </units>
443
443
  </entry>
444
444
  <entry id="text" category="typography" title="Text">
445
- <summary>Theme-aware body-copy primitive with tone, family, sizing, and readable-measure props (`maxWidth` / `lineHeight` / `balanced`).</summary>
445
+ <summary>Theme-aware body-copy primitive with tone, family, sizing, and readable-measure props (`maxWidth` / `lineHeight` / `wrap`).</summary>
446
446
  <units>
447
447
  <unit kind="meta" href="components/text/meta.xml" />
448
448
  <unit kind="a11y" href="components/text/a11y.xml" />
@@ -472,7 +472,7 @@
472
472
  </units>
473
473
  </entry>
474
474
  <entry id="title" category="typography" title="Title">
475
- <summary>Theme-aware heading and display typography with readable-measure props (`maxWidth` / `lineHeight` / `balanced`).</summary>
475
+ <summary>Theme-aware heading and display typography with readable-measure props (`maxWidth` / `lineHeight` / `wrap`).</summary>
476
476
  <units>
477
477
  <unit kind="meta" href="components/title/meta.xml" />
478
478
  <unit kind="a11y" href="components/title/a11y.xml" />
@@ -511,6 +511,16 @@
511
511
  <unit kind="patterns" href="components/toggle-group/patterns.xml" />
512
512
  </units>
513
513
  </entry>
514
+ <entry id="toggle-text-button" category="action" title="ToggleTextButton">
515
+ <summary>Labeled two-state toggle on Base UI Toggle with Button chrome; visible text children, optional addon / pressedVariant; no resting/pressed icon pair.</summary>
516
+ <units>
517
+ <unit kind="meta" href="components/toggle-text-button/meta.xml" />
518
+ <unit kind="a11y" href="components/toggle-text-button/a11y.xml" />
519
+ <unit kind="props" href="components/toggle-text-button/props.xml" />
520
+ <unit kind="usage" href="components/toggle-text-button/usage.xml" />
521
+ <unit kind="patterns" href="components/toggle-text-button/patterns.xml" />
522
+ </units>
523
+ </entry>
514
524
  <entry id="typewriter" category="typography" title="Typewriter">
515
525
  <summary>Decorative inline typewriter animation that inherits typography and color from a parent text primitive.</summary>
516
526
  <units>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viraui/react",
3
- "version": "2026.9.0",
3
+ "version": "2026.10.0-next.0",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist"
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "devDependencies": {
30
30
  "@base-ui/react": ">=1.8.0",
31
- "@viraui/foundation": "2026.9.0",
31
+ "@viraui/foundation": "2026.10.0-next.0",
32
32
  "@viraui/icons": "2026.1.0",
33
33
  "@viraui/postcss-config": "2026.0.0",
34
34
  "@viraui/tsconfig": "2026.0.0",