@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
package/dist/catalog.json CHANGED
@@ -185,7 +185,7 @@
185
185
  {
186
186
  "id": "prose",
187
187
  "name": "Prose",
188
- "summary": "Text-wall grouping region (articles, docs, privacy) with heading-aware em rhythm; reading column via parent Stack `maxWidth` sized for body Text `size`.",
188
+ "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`.",
189
189
  "agentDocs": "./specs/components/prose/meta.xml"
190
190
  },
191
191
  {
@@ -263,7 +263,7 @@
263
263
  {
264
264
  "id": "text",
265
265
  "name": "Text",
266
- "summary": "Theme-aware body-copy primitive with tone, family, sizing, and readable-measure props (`maxWidth` / `lineHeight` / `balanced`).",
266
+ "summary": "Theme-aware body-copy primitive with tone, family, sizing, and readable-measure props (`maxWidth` / `lineHeight` / `wrap`).",
267
267
  "agentDocs": "./specs/components/text/meta.xml"
268
268
  },
269
269
  {
@@ -281,7 +281,7 @@
281
281
  {
282
282
  "id": "title",
283
283
  "name": "Title",
284
- "summary": "Theme-aware heading and display typography with readable-measure props (`maxWidth` / `lineHeight` / `balanced`).",
284
+ "summary": "Theme-aware heading and display typography with readable-measure props (`maxWidth` / `lineHeight` / `wrap`).",
285
285
  "agentDocs": "./specs/components/title/meta.xml"
286
286
  },
287
287
  {
@@ -302,6 +302,12 @@
302
302
  "summary": "Toolbar toggle set with shared selection state; Stack direction follows orientation with zero default gap.",
303
303
  "agentDocs": "./specs/components/toggle-group/meta.xml"
304
304
  },
305
+ {
306
+ "id": "toggle-text-button",
307
+ "name": "ToggleTextButton",
308
+ "summary": "Labeled two-state toggle on Base UI Toggle with Button chrome; visible text children, optional addon / pressedVariant; no resting/pressed icon pair.",
309
+ "agentDocs": "./specs/components/toggle-text-button/meta.xml"
310
+ },
305
311
  {
306
312
  "id": "typewriter",
307
313
  "name": "Typewriter",
@@ -48,5 +48,6 @@ export * from './title';
48
48
  export * from './toast';
49
49
  export * from './toggle-button';
50
50
  export * from './toggle-group';
51
+ export * from './toggle-text-button';
51
52
  export * from './typewriter';
52
53
  export * from './tooltip';
@@ -1 +1 @@
1
- @layer viraui.preflight, viraui.components, viraui.overrides;@layer viraui.components{@property --vui-prose-align{syntax:"*";inherits:false;initial-value:start}@property --vui-prose-gap-blocks{syntax:"<length>";inherits:true;initial-value:2em}@property --vui-prose-gap-heading-start{syntax:"<length>";inherits:true;initial-value:1.3em}@property --vui-prose-gap-heading-end{syntax:"<length>";inherits:true;initial-value:.8em}.viraui__prose-module__Prose_8yO9h{--vui-prose-gap-blocks:2em;--vui-prose-gap-heading-start:1.3em;--vui-prose-gap-heading-end:0.8em;&[data-prose-gap=s]{--vui-prose-gap-blocks:1em;--vui-prose-gap-heading-start:0.7em;--vui-prose-gap-heading-end:0.3em}&[data-prose-gap=m]{--vui-prose-gap-blocks:1.5em;--vui-prose-gap-heading-start:1em;--vui-prose-gap-heading-end:0.6em}&[data-prose-has-align=true]{text-align:var(--vui-prose-align)}&>:is(h1,h2,h3,h4,h5,h6,hgroup,ul,ol,dd,p,figure,img,pre,table,fieldset,blockquote,picture,div):where(:not(:first-child)){margin-block:var(--vui-prose-gap-blocks)}&>:is(h1,h2,h3,h4,h5,h6)+*{margin-block-start:var(--vui-prose-gap-heading-start)}&>:is(h1,h2,h3,h4,h5,h6){margin-block-end:var(--vui-prose-gap-heading-end);text-box:trim-both cap alphabetic}}}
1
+ @layer viraui.preflight, viraui.components, viraui.overrides;@layer viraui.components{@property --vui-prose-align{syntax:"*";inherits:false;initial-value:start}@property --vui-prose-wrap{syntax:"*";inherits:true;initial-value:pretty}@property --vui-prose-gap-blocks{syntax:"<length>";inherits:true;initial-value:2em}@property --vui-prose-gap-heading-start{syntax:"<length>";inherits:true;initial-value:1.3em}@property --vui-prose-gap-heading-end{syntax:"<length>";inherits:true;initial-value:.8em}.viraui__prose-module__Prose_8yO9h{--vui-prose-gap-blocks:2em;--vui-prose-gap-heading-start:1.3em;--vui-prose-gap-heading-end:0.8em;&[data-prose-gap=s]{--vui-prose-gap-blocks:1em;--vui-prose-gap-heading-start:0.7em;--vui-prose-gap-heading-end:0.3em}&[data-prose-gap=m]{--vui-prose-gap-blocks:1.5em;--vui-prose-gap-heading-start:1em;--vui-prose-gap-heading-end:0.6em}text-wrap:var(--vui-prose-wrap);&[data-prose-has-align=true]{text-align:var(--vui-prose-align)}&>:is(h1,h2,h3,h4,h5,h6,hgroup,ul,ol,dd,p,figure,img,pre,table,fieldset,blockquote,picture,div):where(:not(:first-child)){margin-block:var(--vui-prose-gap-blocks)}&>:is(h1,h2,h3,h4,h5,h6)+*{margin-block-start:var(--vui-prose-gap-heading-start)}&>:is(h1,h2,h3,h4,h5,h6){margin-block-end:var(--vui-prose-gap-heading-end);text-box:trim-both cap alphabetic}}}
@@ -25,6 +25,14 @@ export type ProseProps = React.ComponentPropsWithoutRef<'div'> & {
25
25
  * @defaultValue Inherits parent `text-align`.
26
26
  */
27
27
  align?: React.CSSProperties['textAlign'];
28
+ /**
29
+ * Controls CSS `text-wrap` on the Prose root (inherits to descendants unless a child overrides).
30
+ * Default `pretty` for orphan-aware article walls. Prefer `balance` only on short multi-line
31
+ * blocks that need even line lengths. Single-line metrics stay on child Text/Title `whiteSpace`.
32
+ *
33
+ * @defaultValue 'pretty'
34
+ */
35
+ wrap?: React.CSSProperties['textWrap'];
28
36
  /**
29
37
  * Replaces the default `div` root using Base UI render semantics.
30
38
  *
@@ -4,10 +4,11 @@ import { clsx } from "../../core/clsx.js";
4
4
  import prose_module_default from "./prose.module.js";
5
5
  import { useRender } from "@base-ui/react/use-render";
6
6
  //#region src/components/prose/prose.tsx
7
- var Prose = ({ align, className, gap = "l", ref, render, style, ...otherProps }) => {
7
+ var Prose = ({ align, className, gap = "l", ref, render, style, wrap = "pretty", ...otherProps }) => {
8
8
  const hasAlign = align !== void 0;
9
9
  const dynamicStyle = {
10
10
  ...hasAlign && { "--vui-prose-align": align },
11
+ "--vui-prose-wrap": wrap,
11
12
  ...style
12
13
  };
13
14
  return useRender({
@@ -3,31 +3,37 @@ import { Stack } from "../stack/stack.js";
3
3
  import { Surface } from "../surface/surface.js";
4
4
  import tabs_module_default from "./tabs.module.js";
5
5
  import { TabsIndicator } from "./tabs-indicator.js";
6
+ import { useTabsSize } from "./tabs-size-context.js";
6
7
  import { jsx, jsxs } from "react/jsx-runtime";
7
8
  import { Tabs } from "@base-ui/react/tabs";
8
9
  //#region src/components/tabs/tabs-list.tsx
9
- var TabsList = ({ children, className, activateOnFocus = true, ref, ...otherProps }) => /* @__PURE__ */ jsxs(Tabs.List, {
10
- className: clsx(tabs_module_default.List, className),
11
- activateOnFocus,
12
- render: /* @__PURE__ */ jsx(Surface, {
13
- color: 1,
14
- border: "all",
15
- radius: "m",
16
- vPadding: "2xs"
17
- }),
18
- ref,
19
- ...otherProps,
20
- children: [/* @__PURE__ */ jsx(TabsIndicator, {}), /* @__PURE__ */ jsx("div", {
21
- className: tabs_module_default.Track,
22
- children: /* @__PURE__ */ jsx(Stack, {
23
- expandChildren: true,
24
- fullWidth: true,
25
- direction: "row",
26
- hAlign: "start",
27
- vAlign: "center",
28
- children
29
- })
30
- })]
31
- });
10
+ var TabsList = ({ children, className, activateOnFocus = true, ref, ...otherProps }) => {
11
+ const size = useTabsSize();
12
+ const listRadius = size === "s" ? "s" : "m";
13
+ return /* @__PURE__ */ jsxs(Tabs.List, {
14
+ className: clsx(tabs_module_default.List, className),
15
+ activateOnFocus,
16
+ "data-size": size,
17
+ render: /* @__PURE__ */ jsx(Surface, {
18
+ color: 1,
19
+ border: "all",
20
+ radius: listRadius,
21
+ vPadding: "2xs"
22
+ }),
23
+ ref,
24
+ ...otherProps,
25
+ children: [/* @__PURE__ */ jsx(TabsIndicator, {}), /* @__PURE__ */ jsx("div", {
26
+ className: tabs_module_default.Track,
27
+ children: /* @__PURE__ */ jsx(Stack, {
28
+ expandChildren: true,
29
+ fullWidth: true,
30
+ direction: "row",
31
+ hAlign: "start",
32
+ vAlign: "center",
33
+ children
34
+ })
35
+ })]
36
+ });
37
+ };
32
38
  //#endregion
33
39
  export { TabsList };
@@ -0,0 +1,4 @@
1
+ import { ButtonProps } from '../button/button';
2
+ export type TabsSize = NonNullable<ButtonProps['size']>;
3
+ export declare const TabsSizeContext: import('react').Context<TabsSize>;
4
+ export declare const useTabsSize: () => TabsSize;
@@ -0,0 +1,7 @@
1
+ "use client";
2
+ import { createContext, useContext } from "react";
3
+ //#region src/components/tabs/tabs-size-context.ts
4
+ var TabsSizeContext = createContext("m");
5
+ var useTabsSize = () => useContext(TabsSizeContext);
6
+ //#endregion
7
+ export { TabsSizeContext, useTabsSize };
@@ -2,24 +2,29 @@ import { clsx } from "../../core/clsx.js";
2
2
  import { Text } from "../text/text.js";
3
3
  import { Button } from "../button/button.js";
4
4
  import tabs_module_default from "./tabs.module.js";
5
+ import { useTabsSize } from "./tabs-size-context.js";
5
6
  import { jsx } from "react/jsx-runtime";
6
7
  import { Tabs } from "@base-ui/react/tabs";
7
8
  //#region src/components/tabs/tabs-tab.tsx
8
- var TabsTab = ({ addon, addonPosition = "start", children, className, ...otherProps }) => /* @__PURE__ */ jsx(Tabs.Tab, {
9
- render: /* @__PURE__ */ jsx(Button, {
10
- variant: "flat",
11
- addon,
12
- addonPosition,
13
- className: clsx(tabs_module_default.Item, className)
14
- }),
15
- ...otherProps,
16
- children: /* @__PURE__ */ jsx(Text, {
17
- fluid: false,
18
- tone: "muted",
19
- whiteSpace: "nowrap",
20
- className: tabs_module_default.Label,
21
- children
22
- })
23
- });
9
+ var TabsTab = ({ addon, addonPosition = "start", children, className, ...otherProps }) => {
10
+ const size = useTabsSize();
11
+ return /* @__PURE__ */ jsx(Tabs.Tab, {
12
+ render: /* @__PURE__ */ jsx(Button, {
13
+ variant: "flat",
14
+ size,
15
+ addon,
16
+ addonPosition,
17
+ className: clsx(tabs_module_default.Item, className)
18
+ }),
19
+ ...otherProps,
20
+ children: /* @__PURE__ */ jsx(Text, {
21
+ fluid: false,
22
+ tone: "muted",
23
+ whiteSpace: "nowrap",
24
+ className: tabs_module_default.Label,
25
+ children
26
+ })
27
+ });
28
+ };
24
29
  //#endregion
25
30
  export { TabsTab };
@@ -1,5 +1,6 @@
1
1
  import { TabsRoot } from '@base-ui/react/tabs';
2
2
  import { StackProps } from '..';
3
+ import { ButtonProps } from '../button/button';
3
4
  import { TabsListProps } from './tabs-list';
4
5
  import { TabsPanelProps } from './tabs-panel';
5
6
  import { TabsTabProps } from './tabs-tab';
@@ -20,6 +21,12 @@ export type TabsProps = TabsRoot.Props & {
20
21
  * @defaultValue 'start'
21
22
  */
22
23
  listAlignment?: Extract<StackProps['hAlign'], 'start' | 'center' | 'end' | 'stretch'>;
24
+ /**
25
+ * Shared visual size for tab items (`Button`) and the active indicator footprint.
26
+ *
27
+ * @defaultValue 'm'
28
+ */
29
+ size?: ButtonProps['size'];
23
30
  };
24
31
  type TabsComponent = React.FC<TabsProps> & {
25
32
  List: React.FC<TabsListProps>;
@@ -2,6 +2,7 @@ import './tabs.css';
2
2
  import { clsx } from "../../core/clsx.js";
3
3
  import { Stack } from "../stack/stack.js";
4
4
  import tabs_module_default from "./tabs.module.js";
5
+ import { TabsSizeContext } from "./tabs-size-context.js";
5
6
  import { TabsList } from "./tabs-list.js";
6
7
  import { TabsPanel } from "./tabs-panel.js";
7
8
  import { TabsTab } from "./tabs-tab.js";
@@ -10,14 +11,17 @@ import { jsx } from "react/jsx-runtime";
10
11
  import "react";
11
12
  import { Tabs } from "@base-ui/react/tabs";
12
13
  //#region src/components/tabs/tabs.tsx
13
- var TabsRootComponent = ({ children, className, listAlignment = "start", ...otherProps }) => /* @__PURE__ */ jsx(Tabs.Root, {
14
- render: /* @__PURE__ */ jsx(Stack, {
15
- hAlign: listAlignment,
16
- vAlign: "start",
17
- className: clsx(tabs_module_default.Tabs, className)
18
- }),
19
- ...otherProps,
20
- children
14
+ var TabsRootComponent = ({ children, className, listAlignment = "start", size = "m", ...otherProps }) => /* @__PURE__ */ jsx(TabsSizeContext.Provider, {
15
+ value: size,
16
+ children: /* @__PURE__ */ jsx(Tabs.Root, {
17
+ render: /* @__PURE__ */ jsx(Stack, {
18
+ hAlign: listAlignment,
19
+ vAlign: "start",
20
+ className: clsx(tabs_module_default.Tabs, className)
21
+ }),
22
+ ...otherProps,
23
+ children
24
+ })
21
25
  });
22
26
  var Tabs$1 = Object.assign(TabsRootComponent, {
23
27
  List: TabsList,
@@ -1,5 +1,5 @@
1
- @layer viraui.preflight, viraui.components, viraui.overrides;@layer viraui.components{@property --vui-text-align{syntax:"*";inherits:false;initial-value:start}@property --vui-text-max-width{syntax:"*";inherits:false;initial-value:none}@property --vui-text-literal-color{syntax:"<color>";inherits:false;initial-value:#0000}@property --vui-text-white-space{syntax:"*";inherits:false;initial-value:normal}@property --vui-text-letter-spacing{syntax:"normal | <length>";inherits:false;initial-value:normal}@property --vui-text-text-transform{syntax:"none | capitalize | uppercase | lowercase";inherits:false;initial-value:none}@property --vui-text-font-weight{syntax:"<number>";inherits:false;initial-value:400}@property --vui-text-font-width{syntax:"<number>";inherits:false;initial-value:100}@property --vui-text-font-opsz{syntax:"<number>";inherits:false;initial-value:16}@property --vui-text-trim{syntax:"*";inherits:false;initial-value:trim-both}@property --vui-text-trim-type{syntax:"*";inherits:false;initial-value:cap alphabetic}@property --vui-text-padding-inline-start{syntax:"<length>";inherits:false;initial-value:0}@property --vui-text-padding-inline-end{syntax:"<length>";inherits:false;initial-value:0}@property --vui-text-padding-block-start{syntax:"<length>";inherits:false;initial-value:0}@property --vui-text-padding-block-end{syntax:"<length>";inherits:false;initial-value:0}@property --vui-text-font-size{syntax:"<length>";inherits:false;initial-value:1rem}@property --vui-text-font-family{syntax:"*";inherits:false;initial-value:sans-serif}@property --vui-text-line-height{syntax:"normal | <number> | <length-percentage>";inherits:false;initial-value:normal}@property --__text-fluid{syntax:"<number>";inherits:false;initial-value:0}@property --__text-size-min{syntax:"<length>";inherits:false;initial-value:1rem}@property --__text-size-max{syntax:"<length>";inherits:false;initial-value:1rem}.viraui__text-module__Text_bS8kv{--__text-fluid:calc((100vi - 320px) / 1125px);--__text-size-min:var(--font-scale-body-s);--__text-size-max:var(--font-scale-body-m);--vui-text-font-size:clamp(
1
+ @layer viraui.preflight, viraui.components, viraui.overrides;@layer viraui.components{@property --vui-text-align{syntax:"*";inherits:false;initial-value:start}@property --vui-text-max-width{syntax:"*";inherits:false;initial-value:none}@property --vui-text-literal-color{syntax:"<color>";inherits:false;initial-value:#0000}@property --vui-text-white-space{syntax:"*";inherits:false;initial-value:normal}@property --vui-text-wrap{syntax:"*";inherits:false;initial-value:pretty}@property --vui-text-letter-spacing{syntax:"normal | <length>";inherits:false;initial-value:normal}@property --vui-text-text-transform{syntax:"none | capitalize | uppercase | lowercase";inherits:false;initial-value:none}@property --vui-text-font-weight{syntax:"<number>";inherits:false;initial-value:400}@property --vui-text-font-width{syntax:"<number>";inherits:false;initial-value:100}@property --vui-text-font-opsz{syntax:"<number>";inherits:false;initial-value:16}@property --vui-text-trim{syntax:"*";inherits:false;initial-value:trim-both}@property --vui-text-trim-type{syntax:"*";inherits:false;initial-value:cap alphabetic}@property --vui-text-padding-inline-start{syntax:"<length>";inherits:false;initial-value:0}@property --vui-text-padding-inline-end{syntax:"<length>";inherits:false;initial-value:0}@property --vui-text-padding-block-start{syntax:"<length>";inherits:false;initial-value:0}@property --vui-text-padding-block-end{syntax:"<length>";inherits:false;initial-value:0}@property --vui-text-font-size{syntax:"<length>";inherits:false;initial-value:1rem}@property --vui-text-font-family{syntax:"*";inherits:false;initial-value:sans-serif}@property --vui-text-line-height{syntax:"normal | <number> | <length-percentage>";inherits:false;initial-value:normal}@property --__text-fluid{syntax:"<number>";inherits:false;initial-value:0}@property --__text-size-min{syntax:"<length>";inherits:false;initial-value:1rem}@property --__text-size-max{syntax:"<length>";inherits:false;initial-value:1rem}.viraui__text-module__Text_bS8kv{--__text-fluid:calc((100vi - 320px) / 1125px);--__text-size-min:var(--font-scale-body-s);--__text-size-max:var(--font-scale-body-m);--vui-text-font-size:clamp(
2
2
  var(--__text-size-min),
3
3
  calc(var(--__text-size-min) + (var(--__text-size-max) - var(--__text-size-min)) * var(--__text-fluid)),
4
4
  var(--__text-size-max)
5
- );--vui-text-font-family:var(--font-family-body);--vui-text-line-height:var(--font-leading-m);margin:0;max-inline-size:var(--vui-text-max-width);font-family:var(--vui-text-font-family);font-size:var(--vui-text-font-size);line-height:var(--vui-text-line-height);letter-spacing:var(--vui-text-letter-spacing);text-transform:var(--vui-text-text-transform);white-space:var(--vui-text-white-space);word-break:break-word;font-weight:var(--vui-text-font-weight);font-stretch:calc(var(--vui-text-font-width) * 1%);font-optical-sizing:auto;font-variation-settings:"wght" var(--vui-text-font-weight),"wdth" var(--vui-text-font-width),"opsz" var(--vui-text-font-opsz);text-box:var(--vui-text-trim) var(--vui-text-trim-type);&[data-text-balanced=true]{text-wrap:balance}&[data-text-selection=false]{-webkit-user-select:none;user-select:none}&[data-text-has-align=true]{text-align:var(--vui-text-align)}&[data-text-has-padding=true]{padding:var(--vui-text-padding-block-start) var(--vui-text-padding-inline-end) var(--vui-text-padding-block-end) var(--vui-text-padding-inline-start)}&[data-text-family=mono]{--vui-text-font-family:var(--font-family-mono)}&[data-text-size="4xl"]{--__text-size-min:var(--font-scale-body-3xl);--__text-size-max:var(--font-scale-body-4xl)}&[data-text-size="3xl"]{--__text-size-min:var(--font-scale-body-2xl);--__text-size-max:var(--font-scale-body-3xl)}&[data-text-size="2xl"]{--__text-size-min:var(--font-scale-body-xl);--__text-size-max:var(--font-scale-body-2xl)}&[data-text-size=xl]{--__text-size-min:var(--font-scale-body-l);--__text-size-max:var(--font-scale-body-xl)}&[data-text-size=l]{--__text-size-min:var(--font-scale-body-m);--__text-size-max:var(--font-scale-body-l)}&[data-text-size=s]{--__text-size-min:var(--font-scale-body-s);--__text-size-max:var(--font-scale-body-s)}&[data-text-size=xs]{--__text-size-min:var(--font-scale-body-xs);--__text-size-max:var(--font-scale-body-xs)}& .viraui__text-module__Text_bS8kv:not([data-text-size]){font-size:inherit}& .viraui__text-module__Text_bS8kv:not([data-text-line-height]){line-height:inherit}&[data-text-fluid=false]{--vui-text-font-size:var(--__text-size-max)}@container root style(--effect-fluid-typography: initial){--vui-text-font-size:var(--__text-size-max)}&[data-text-weight=light]{--vui-text-font-weight:200}&[data-text-weight=semibold]{--vui-text-font-weight:600}&[data-text-weight=bold]{--vui-text-font-weight:800}&[data-text-tone=primary]{color:var(--global-primary)}&[data-text-tone=muted]{color:var(--global-muted)}&[data-text-tone=interactive]{color:var(--global-interactive-text)}&[data-text-tone=positive]{color:var(--highlight-green)}&[data-text-tone=informative]{color:var(--highlight-blue)}&[data-text-tone=danger]{color:var(--highlight-red)}&[data-text-tone=warning]{color:var(--highlight-yellow)}&[data-text-has-literal-color=true]{color:var(--vui-text-literal-color)}&[data-text-line-height=none]{--vui-text-line-height:0}&[data-text-line-height=xs]{--vui-text-line-height:var(--font-leading-xs)}&[data-text-line-height=s]{--vui-text-line-height:var(--font-leading-s)}&[data-text-line-height=m]{--vui-text-line-height:var(--font-leading-m)}&[data-text-line-height=l]{--vui-text-line-height:var(--font-leading-l)}}}
5
+ );--vui-text-font-family:var(--font-family-body);--vui-text-line-height:var(--font-leading-m);margin:0;max-inline-size:var(--vui-text-max-width);font-family:var(--vui-text-font-family);font-size:var(--vui-text-font-size);line-height:var(--vui-text-line-height);letter-spacing:var(--vui-text-letter-spacing);text-transform:var(--vui-text-text-transform);text-wrap:var(--vui-text-wrap);white-space:var(--vui-text-white-space);word-break:break-word;font-weight:var(--vui-text-font-weight);font-stretch:calc(var(--vui-text-font-width) * 1%);font-optical-sizing:auto;font-variation-settings:"wght" var(--vui-text-font-weight),"wdth" var(--vui-text-font-width),"opsz" var(--vui-text-font-opsz);text-box:var(--vui-text-trim) var(--vui-text-trim-type);&[data-text-selection=false]{-webkit-user-select:none;user-select:none}&[data-text-has-align=true]{text-align:var(--vui-text-align)}&[data-text-has-padding=true]{padding:var(--vui-text-padding-block-start) var(--vui-text-padding-inline-end) var(--vui-text-padding-block-end) var(--vui-text-padding-inline-start)}&[data-text-family=mono]{--vui-text-font-family:var(--font-family-mono)}&[data-text-size="4xl"]{--__text-size-min:var(--font-scale-body-3xl);--__text-size-max:var(--font-scale-body-4xl)}&[data-text-size="3xl"]{--__text-size-min:var(--font-scale-body-2xl);--__text-size-max:var(--font-scale-body-3xl)}&[data-text-size="2xl"]{--__text-size-min:var(--font-scale-body-xl);--__text-size-max:var(--font-scale-body-2xl)}&[data-text-size=xl]{--__text-size-min:var(--font-scale-body-l);--__text-size-max:var(--font-scale-body-xl)}&[data-text-size=l]{--__text-size-min:var(--font-scale-body-m);--__text-size-max:var(--font-scale-body-l)}&[data-text-size=s]{--__text-size-min:var(--font-scale-body-s);--__text-size-max:var(--font-scale-body-s)}&[data-text-size=xs]{--__text-size-min:var(--font-scale-body-xs);--__text-size-max:var(--font-scale-body-xs)}& .viraui__text-module__Text_bS8kv:not([data-text-size]){font-size:inherit}& .viraui__text-module__Text_bS8kv:not([data-text-line-height]){line-height:inherit}&[data-text-fluid=false]{--vui-text-font-size:var(--__text-size-max)}@container root style(--effect-fluid-typography: initial){--vui-text-font-size:var(--__text-size-max)}&[data-text-weight=light]{--vui-text-font-weight:200}&[data-text-weight=semibold]{--vui-text-font-weight:600}&[data-text-weight=bold]{--vui-text-font-weight:800}&[data-text-tone=primary]{color:var(--global-primary)}&[data-text-tone=muted]{color:var(--global-muted)}&[data-text-tone=interactive]{color:var(--global-interactive-text)}&[data-text-tone=positive]{color:var(--highlight-green)}&[data-text-tone=informative]{color:var(--highlight-blue)}&[data-text-tone=danger]{color:var(--highlight-red)}&[data-text-tone=warning]{color:var(--highlight-yellow)}&[data-text-has-literal-color=true]{color:var(--vui-text-literal-color)}&[data-text-line-height=none]{--vui-text-line-height:0}&[data-text-line-height=xs]{--vui-text-line-height:var(--font-leading-xs)}&[data-text-line-height=s]{--vui-text-line-height:var(--font-leading-s)}&[data-text-line-height=m]{--vui-text-line-height:var(--font-leading-m)}&[data-text-line-height=l]{--vui-text-line-height:var(--font-leading-l)}}}
@@ -85,7 +85,9 @@ export type TextProps = React.ComponentPropsWithoutRef<'span'> & {
85
85
  */
86
86
  maxWidth?: string;
87
87
  /**
88
- * Controls white-space preservation and wrapping behavior.
88
+ * Controls white-space collapse/preserve (`white-space`).
89
+ * Prefer `whiteSpace="nowrap"` for single-line metrics/chrome (also collapses spaces).
90
+ * For wrapping algorithm / even line lengths use `wrap` (`text-wrap`) — not this prop.
89
91
  *
90
92
  * @defaultValue 'normal'
91
93
  */
@@ -105,13 +107,14 @@ export type TextProps = React.ComponentPropsWithoutRef<'span'> & {
105
107
  */
106
108
  transform?: React.CSSProperties['textTransform'];
107
109
  /**
108
- * Enables balanced wrapping for block-like copy.
109
- * Prefer on short wrapping UI paragraphs (hero lead, empty state, card blurb);
110
- * omit when single-line or intentional ragged.
110
+ * Controls CSS `text-wrap` (wrapping algorithm / wrap on-off).
111
+ * Default `pretty` for orphan-aware UI copy. Prefer `balance` on short multi-line
112
+ * heads/blurbs that need even line lengths. Use `wrap="nowrap"` only when you need
113
+ * no wrap without changing white-space collapse; prefer `whiteSpace="nowrap"` for metrics.
111
114
  *
112
- * @defaultValue false
115
+ * @defaultValue 'pretty'
113
116
  */
114
- balanced?: boolean;
117
+ wrap?: React.CSSProperties['textWrap'];
115
118
  /**
116
119
  * Enables text selection on the rendered copy.
117
120
  *
@@ -4,7 +4,7 @@ import { resolveAxisPadding } from "../../core/styles/resolve-axis-padding.js";
4
4
  import text_module_default from "./text.module.js";
5
5
  import { useRender } from "@base-ui/react/use-render";
6
6
  //#region src/components/text/text.tsx
7
- var Text = ({ align, balanced = false, selection = true, className, color, family = "body", fluid = false, fontWidth, hPadding, lineHeight, maxWidth, opticalSize, ref, render, size, style, tone, tracking, transform, trim = "both", trimType = "cap alphabetic", vPadding, weight = "regular", whiteSpace = "normal", ...otherProps }) => {
7
+ var Text = ({ align, selection = true, className, color, family = "body", fluid = false, fontWidth, hPadding, lineHeight, maxWidth, opticalSize, ref, render, size, style, tone, tracking, transform, trim = "both", trimType = "cap alphabetic", vPadding, weight = "regular", whiteSpace = "normal", wrap = "pretty", ...otherProps }) => {
8
8
  const hasAlign = align !== void 0;
9
9
  const hasPadding = Boolean(hPadding || vPadding);
10
10
  const dynamicStyle = {
@@ -12,6 +12,7 @@ var Text = ({ align, balanced = false, selection = true, className, color, famil
12
12
  "--vui-text-max-width": maxWidth ?? "none",
13
13
  "--vui-text-literal-color": color,
14
14
  "--vui-text-white-space": whiteSpace,
15
+ "--vui-text-wrap": wrap,
15
16
  "--vui-text-letter-spacing": tracking,
16
17
  "--vui-text-text-transform": transform,
17
18
  "--vui-text-trim": trim ? `trim-${trim}` : "none",
@@ -34,7 +35,6 @@ var Text = ({ align, balanced = false, selection = true, className, color, famil
34
35
  "data-text-weight": typeof weight === "string" ? weight : "custom",
35
36
  "data-text-tone": tone,
36
37
  "data-text-line-height": lineHeight,
37
- "data-text-balanced": balanced ? "true" : "false",
38
38
  "data-text-selection": selection ? "true" : "false",
39
39
  "data-text-fluid": fluid ? "true" : "false",
40
40
  "data-text-has-align": hasAlign ? "true" : "false",
@@ -1,5 +1,5 @@
1
- @layer viraui.preflight, viraui.components, viraui.overrides;@layer viraui.components{@property --vui-title-align{syntax:"*";inherits:false;initial-value:start}@property --vui-title-max-width{syntax:"*";inherits:false;initial-value:none}@property --vui-title-literal-color{syntax:"<color>";inherits:false;initial-value:#0000}@property --vui-title-white-space{syntax:"*";inherits:false;initial-value:normal}@property --vui-title-font-weight{syntax:"<number>";inherits:false;initial-value:500}@property --vui-title-font-width{syntax:"<number>";inherits:false;initial-value:100}@property --vui-title-font-opsz{syntax:"<number>";inherits:false;initial-value:16}@property --vui-title-trim{syntax:"*";inherits:false;initial-value:trim-both}@property --vui-title-trim-type{syntax:"*";inherits:false;initial-value:cap alphabetic}@property --vui-title-padding-inline-start{syntax:"<length>";inherits:false;initial-value:0}@property --vui-title-padding-inline-end{syntax:"<length>";inherits:false;initial-value:0}@property --vui-title-padding-block-start{syntax:"<length>";inherits:false;initial-value:0}@property --vui-title-padding-block-end{syntax:"<length>";inherits:false;initial-value:0}@property --vui-title-font-size{syntax:"<length>";inherits:false;initial-value:1rem}@property --vui-title-line-height{syntax:"normal | <number> | <length-percentage>";inherits:false;initial-value:normal}@property --vui-title-letter-spacing{syntax:"normal | <length>";inherits:false;initial-value:normal}@property --__title-letter-spacing{syntax:"normal | <length>";inherits:false;initial-value:normal}@property --vui-title-text-transform{syntax:"none | capitalize | uppercase | lowercase";inherits:false;initial-value:none}@property --vui-title-font-family{syntax:"*";inherits:false;initial-value:sans-serif}@property --__title-fluid{syntax:"<number>";inherits:false;initial-value:0}@property --__title-size-min{syntax:"<length>";inherits:false;initial-value:1rem}@property --__title-size-max{syntax:"<length>";inherits:false;initial-value:1rem}.viraui__title-module__Title_CXMUX{--__title-fluid:calc((100vi - 320px) / 1125px);--__title-size-min:var(--font-scale-h2);--__title-size-max:var(--font-scale-h1);--vui-title-font-size:clamp(
1
+ @layer viraui.preflight, viraui.components, viraui.overrides;@layer viraui.components{@property --vui-title-align{syntax:"*";inherits:false;initial-value:start}@property --vui-title-max-width{syntax:"*";inherits:false;initial-value:none}@property --vui-title-literal-color{syntax:"<color>";inherits:false;initial-value:#0000}@property --vui-title-white-space{syntax:"*";inherits:false;initial-value:normal}@property --vui-title-wrap{syntax:"*";inherits:false;initial-value:pretty}@property --vui-title-font-weight{syntax:"<number>";inherits:false;initial-value:500}@property --vui-title-font-width{syntax:"<number>";inherits:false;initial-value:100}@property --vui-title-font-opsz{syntax:"<number>";inherits:false;initial-value:16}@property --vui-title-trim{syntax:"*";inherits:false;initial-value:trim-both}@property --vui-title-trim-type{syntax:"*";inherits:false;initial-value:cap alphabetic}@property --vui-title-padding-inline-start{syntax:"<length>";inherits:false;initial-value:0}@property --vui-title-padding-inline-end{syntax:"<length>";inherits:false;initial-value:0}@property --vui-title-padding-block-start{syntax:"<length>";inherits:false;initial-value:0}@property --vui-title-padding-block-end{syntax:"<length>";inherits:false;initial-value:0}@property --vui-title-font-size{syntax:"<length>";inherits:false;initial-value:1rem}@property --vui-title-line-height{syntax:"normal | <number> | <length-percentage>";inherits:false;initial-value:normal}@property --vui-title-letter-spacing{syntax:"normal | <length>";inherits:false;initial-value:normal}@property --__title-letter-spacing{syntax:"normal | <length>";inherits:false;initial-value:normal}@property --vui-title-text-transform{syntax:"none | capitalize | uppercase | lowercase";inherits:false;initial-value:none}@property --vui-title-font-family{syntax:"*";inherits:false;initial-value:sans-serif}@property --__title-fluid{syntax:"<number>";inherits:false;initial-value:0}@property --__title-size-min{syntax:"<length>";inherits:false;initial-value:1rem}@property --__title-size-max{syntax:"<length>";inherits:false;initial-value:1rem}.viraui__title-module__Title_CXMUX{--__title-fluid:calc((100vi - 320px) / 1125px);--__title-size-min:var(--font-scale-h2);--__title-size-max:var(--font-scale-h1);--vui-title-font-size:clamp(
2
2
  var(--__title-size-min),
3
3
  calc(var(--__title-size-min) + (var(--__title-size-max) - var(--__title-size-min)) * var(--__title-fluid)),
4
4
  var(--__title-size-max)
5
- );--vui-title-font-family:var(--font-family-heading);--vui-title-line-height:var(--font-leading-m);--__title-letter-spacing:-0.03em;margin:0;max-inline-size:var(--vui-title-max-width);font-family:var(--vui-title-font-family);font-size:var(--vui-title-font-size);line-height:var(--vui-title-line-height);letter-spacing:var(--__title-letter-spacing);text-transform:var(--vui-title-text-transform);white-space:var(--vui-title-white-space);word-break:break-word;font-weight:var(--vui-title-font-weight);font-stretch:calc(var(--vui-title-font-width) * 1%);font-optical-sizing:auto;font-variation-settings:"wght" var(--vui-title-font-weight),"wdth" var(--vui-title-font-width),"opsz" var(--vui-title-font-opsz);text-box:var(--vui-title-trim) var(--vui-title-trim-type);display:inline-flex;&[data-title-has-align=true]{text-align:var(--vui-title-align)}&[data-title-has-tracking=true]{letter-spacing:var(--vui-title-letter-spacing)}&[data-title-balanced=true]{text-wrap:balance}&[data-title-selection=false]{-webkit-user-select:none;user-select:none}&[data-title-has-padding=true]{padding:var(--vui-title-padding-block-start) var(--vui-title-padding-inline-end) var(--vui-title-padding-block-end) var(--vui-title-padding-inline-start)}&[data-title-size=display]{--__title-size-min:var(--font-scale-h1);--__title-size-max:var(--font-scale-display)}&[data-title-size="2"]{--__title-size-min:var(--font-scale-h3);--__title-size-max:var(--font-scale-h2)}&[data-title-size="3"]{--__title-size-min:var(--font-scale-h4);--__title-size-max:var(--font-scale-h3)}&[data-title-size="4"]{--__title-size-min:var(--font-scale-h5);--__title-size-max:var(--font-scale-h4)}&[data-title-size="5"]{--__title-size-min:var(--font-scale-h6);--__title-size-max:var(--font-scale-h5);--__title-letter-spacing:-0.02em}&[data-title-size="6"]{--__title-size-min:var(--font-scale-body-m);--__title-size-max:var(--font-scale-h6);--__title-letter-spacing:-0.02em}&[data-title-fluid=false]{--vui-title-font-size:var(--__title-size-max)}@container root style(--effect-fluid-typography: initial){--vui-title-font-size:var(--__title-size-max)}&[data-title-has-literal-color=true]{color:var(--vui-title-literal-color)}&[data-title-line-height=none]{--vui-title-line-height:0}&[data-title-line-height=xs]{--vui-title-line-height:var(--font-leading-xs)}&[data-title-line-height=s]{--vui-title-line-height:var(--font-leading-s)}&[data-title-line-height=m]{--vui-title-line-height:var(--font-leading-m)}&[data-title-line-height=l]{--vui-title-line-height:var(--font-leading-l)}}}
5
+ );--vui-title-font-family:var(--font-family-heading);--vui-title-line-height:var(--font-leading-m);--__title-letter-spacing:-0.03em;margin:0;max-inline-size:var(--vui-title-max-width);font-family:var(--vui-title-font-family);font-size:var(--vui-title-font-size);line-height:var(--vui-title-line-height);letter-spacing:var(--__title-letter-spacing);text-transform:var(--vui-title-text-transform);text-wrap:var(--vui-title-wrap);white-space:var(--vui-title-white-space);word-break:break-word;font-weight:var(--vui-title-font-weight);font-stretch:calc(var(--vui-title-font-width) * 1%);font-optical-sizing:auto;font-variation-settings:"wght" var(--vui-title-font-weight),"wdth" var(--vui-title-font-width),"opsz" var(--vui-title-font-opsz);text-box:var(--vui-title-trim) var(--vui-title-trim-type);display:inline-flex;&[data-title-has-align=true]{text-align:var(--vui-title-align)}&[data-title-has-tracking=true]{letter-spacing:var(--vui-title-letter-spacing)}&[data-title-selection=false]{-webkit-user-select:none;user-select:none}&[data-title-has-padding=true]{padding:var(--vui-title-padding-block-start) var(--vui-title-padding-inline-end) var(--vui-title-padding-block-end) var(--vui-title-padding-inline-start)}&[data-title-size=display]{--__title-size-min:var(--font-scale-h1);--__title-size-max:var(--font-scale-display)}&[data-title-size="2"]{--__title-size-min:var(--font-scale-h3);--__title-size-max:var(--font-scale-h2)}&[data-title-size="3"]{--__title-size-min:var(--font-scale-h4);--__title-size-max:var(--font-scale-h3)}&[data-title-size="4"]{--__title-size-min:var(--font-scale-h5);--__title-size-max:var(--font-scale-h4)}&[data-title-size="5"]{--__title-size-min:var(--font-scale-h6);--__title-size-max:var(--font-scale-h5);--__title-letter-spacing:-0.02em}&[data-title-size="6"]{--__title-size-min:var(--font-scale-body-m);--__title-size-max:var(--font-scale-h6);--__title-letter-spacing:-0.02em}&[data-title-fluid=false]{--vui-title-font-size:var(--__title-size-max)}@container root style(--effect-fluid-typography: initial){--vui-title-font-size:var(--__title-size-max)}&[data-title-has-literal-color=true]{color:var(--vui-title-literal-color)}&[data-title-line-height=none]{--vui-title-line-height:0}&[data-title-line-height=xs]{--vui-title-line-height:var(--font-leading-xs)}&[data-title-line-height=s]{--vui-title-line-height:var(--font-leading-s)}&[data-title-line-height=m]{--vui-title-line-height:var(--font-leading-m)}&[data-title-line-height=l]{--vui-title-line-height:var(--font-leading-l)}}}
@@ -72,7 +72,9 @@ export type TitleProps = React.ComponentPropsWithoutRef<'span'> & {
72
72
  */
73
73
  maxWidth?: string;
74
74
  /**
75
- * Controls white-space preservation and wrapping behavior.
75
+ * Controls white-space collapse/preserve (`white-space`).
76
+ * Prefer `whiteSpace="nowrap"` for single-line metrics/chrome (also collapses spaces).
77
+ * For wrapping algorithm / even line lengths use `wrap` (`text-wrap`) — not this prop.
76
78
  *
77
79
  * @defaultValue 'normal'
78
80
  */
@@ -94,13 +96,14 @@ export type TitleProps = React.ComponentPropsWithoutRef<'span'> & {
94
96
  */
95
97
  transform?: React.CSSProperties['textTransform'];
96
98
  /**
97
- * Enables balanced wrapping for block-like titles.
98
- * Prefer on short wrapping heads (hero, empty state, section);
99
- * omit for single-line / `whiteSpace="nowrap"` metrics.
99
+ * Controls CSS `text-wrap` (wrapping algorithm / wrap on-off).
100
+ * Default `pretty` for orphan-aware titles. Prefer `balance` on short multi-line
101
+ * heads that need even line lengths. Use `wrap="nowrap"` only when you need no wrap
102
+ * without changing white-space collapse; prefer `whiteSpace="nowrap"` for metrics.
100
103
  *
101
- * @defaultValue false
104
+ * @defaultValue 'pretty'
102
105
  */
103
- balanced?: boolean;
106
+ wrap?: React.CSSProperties['textWrap'];
104
107
  /**
105
108
  * Enables text selection on the rendered copy.
106
109
  *
@@ -4,7 +4,7 @@ import { resolveAxisPadding } from "../../core/styles/resolve-axis-padding.js";
4
4
  import title_module_default from "./title.module.js";
5
5
  import { useRender } from "@base-ui/react/use-render";
6
6
  //#region src/components/title/title.tsx
7
- var Title = ({ align, balanced = false, selection = true, className, color, fluid = true, fontWidth, hPadding, size = "1", lineHeight = "m", maxWidth, opticalSize, ref, render, style, tracking, transform, trim = "both", trimType = "cap alphabetic", vPadding, weight, whiteSpace = "normal", ...otherProps }) => {
7
+ var Title = ({ align, selection = true, className, color, fluid = true, fontWidth, hPadding, size = "1", lineHeight = "m", maxWidth, opticalSize, ref, render, style, tracking, transform, trim = "both", trimType = "cap alphabetic", vPadding, weight, whiteSpace = "normal", wrap = "pretty", ...otherProps }) => {
8
8
  const hasAlign = align !== void 0;
9
9
  const hasPadding = Boolean(hPadding || vPadding);
10
10
  const hasTracking = tracking !== void 0;
@@ -13,6 +13,7 @@ var Title = ({ align, balanced = false, selection = true, className, color, flui
13
13
  "--vui-title-max-width": maxWidth ?? "none",
14
14
  "--vui-title-literal-color": color,
15
15
  "--vui-title-white-space": whiteSpace,
16
+ "--vui-title-wrap": wrap,
16
17
  ...hasTracking && { "--vui-title-letter-spacing": tracking },
17
18
  "--vui-title-text-transform": transform,
18
19
  "--vui-title-trim": trim ? `trim-${trim}` : "none",
@@ -32,7 +33,6 @@ var Title = ({ align, balanced = false, selection = true, className, color, flui
32
33
  ...otherProps,
33
34
  "data-title-size": size,
34
35
  "data-title-line-height": lineHeight,
35
- "data-title-balanced": balanced ? "true" : "false",
36
36
  "data-title-selection": selection ? "true" : "false",
37
37
  "data-title-fluid": fluid ? "true" : "false",
38
38
  "data-title-has-align": hasAlign ? "true" : "false",
@@ -4,7 +4,9 @@ import { ToggleGroup as ToggleGroupPrimitive } from '@base-ui/react/toggle-group
4
4
  *
5
5
  * Thin wrapper around Base UI `ToggleGroup` with default `Stack` direction
6
6
  * (`row` / `column` from `orientation`) and zero gap — consumers own spacing.
7
- * Compose `ToggleButton` children each with `value` and an accessible name.
7
+ * Compose `ToggleButton` and/or `ToggleTextButton` children each with `value`
8
+ * (icon-only children also need an accessible name).
9
+
8
10
  *
9
11
  * @defaultValue Base UI pass-through props keep upstream defaults.
10
12
  */
@@ -0,0 +1,2 @@
1
+ export { ToggleTextButton } from './toggle-text-button';
2
+ export type { ToggleTextButtonProps } from './toggle-text-button';
@@ -0,0 +1,27 @@
1
+ import { Toggle as TogglePrimitive } from '@base-ui/react/toggle';
2
+ import { ButtonProps } from '../button/button';
3
+ import * as React from 'react';
4
+ /**
5
+ * Public ToggleTextButton props.
6
+ *
7
+ * Labeled two-state control built on Base UI `Toggle` and `Button`.
8
+ * Prefer a visible text `children` label; supply `aria-label` or `aria-labelledby`
9
+ * when children are empty or whitespace-only.
10
+ *
11
+ * @defaultValue Base UI toggle pass-through props keep upstream defaults.
12
+ */
13
+ export type ToggleTextButtonProps = Omit<ButtonProps, 'render' | 'variant'> & Omit<TogglePrimitive.Props, 'render' | 'children'> & {
14
+ /**
15
+ * Visual variant while the toggle is not pressed.
16
+ *
17
+ * @defaultValue 'flat'
18
+ */
19
+ variant?: ButtonProps['variant'];
20
+ /**
21
+ * Visual variant while the toggle is pressed.
22
+ *
23
+ * @defaultValue When omitted, `variant` is used in both states.
24
+ */
25
+ pressedVariant?: ButtonProps['variant'];
26
+ };
27
+ export declare const ToggleTextButton: React.FC<ToggleTextButtonProps>;
@@ -0,0 +1,24 @@
1
+ import { Button } from "../button/button.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import "react";
4
+ import { mergeProps } from "@base-ui/react/merge-props";
5
+ import { Toggle } from "@base-ui/react/toggle";
6
+ //#region src/components/toggle-text-button/toggle-text-button.tsx
7
+ var ToggleTextButton = ({ variant = "flat", pressedVariant, pressed, defaultPressed, onPressedChange, value, disabled, ref, ...otherProps }) => /* @__PURE__ */ jsx(Toggle, {
8
+ ref,
9
+ value,
10
+ pressed,
11
+ defaultPressed,
12
+ onPressedChange,
13
+ disabled,
14
+ render: (toggleProps, state) => {
15
+ const { "aria-label": _toggleAriaLabel, "aria-labelledby": _toggleAriaLabelledBy, ...restToggleProps } = toggleProps;
16
+ return /* @__PURE__ */ jsx(Button, {
17
+ ...mergeProps(restToggleProps, otherProps),
18
+ disabled,
19
+ variant: state.pressed ? pressedVariant ?? variant : variant
20
+ });
21
+ }
22
+ });
23
+ //#endregion
24
+ export { ToggleTextButton };
package/dist/index.js CHANGED
@@ -70,7 +70,8 @@ import { Title } from "./components/title/title.js";
70
70
  import { Toast } from "./components/toast/toast.js";
71
71
  import { ToggleButton } from "./components/toggle-button/toggle-button.js";
72
72
  import { ToggleGroup } from "./components/toggle-group/toggle-group.js";
73
+ import { ToggleTextButton } from "./components/toggle-text-button/toggle-text-button.js";
73
74
  import { Typewriter } from "./components/typewriter/typewriter.js";
74
75
  import { TooltipContent } from "./components/tooltip/tooltip-content.js";
75
76
  import { Tooltip, TooltipProvider, TooltipTrigger } from "./components/tooltip/tooltip.js";
76
- export { Accordion, Annotate, Autocomplete, Avatar, AvatarGroup, Badge, Beam, Bleed, BreakpointsProvider, Button, ButtonLink, Checkbox, CheckboxGroup, Chip, ClampText, Dialog, DialogClose, DialogIndent, DialogIndentBackground, DialogSheet, DialogTrigger, DirectionProvider, Disclosure, ELEVATION_DEFAULTS, Elevator, Fieldset, FitText, Glow, Grid, IconButton, IconButtonLink, LinearProgress, Masonry, Menu, MenuCheckboxItem, MenuGroup, MenuItem, MenuLinkItem, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Meter, NumberField, OTPField, Popover, PopoverClose, PopoverDescription, PopoverSheet, PopoverTitle, PopoverTrigger, ProgressiveBlur, Prose, Radio, RadioGroup, Select, Separator, Shimmer, Skeleton, Slider, Spinner, Stack, StaticNoise, Surface, Switch, Tabs, Text, Textarea, Textfield, Title, Toast, ToggleButton, ToggleGroup, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Typewriter, getElevationProps, useBreakpoints, useDirection };
77
+ export { Accordion, Annotate, Autocomplete, Avatar, AvatarGroup, Badge, Beam, Bleed, BreakpointsProvider, Button, ButtonLink, Checkbox, CheckboxGroup, Chip, ClampText, Dialog, DialogClose, DialogIndent, DialogIndentBackground, DialogSheet, DialogTrigger, DirectionProvider, Disclosure, ELEVATION_DEFAULTS, Elevator, Fieldset, FitText, Glow, Grid, IconButton, IconButtonLink, LinearProgress, Masonry, Menu, MenuCheckboxItem, MenuGroup, MenuItem, MenuLinkItem, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Meter, NumberField, OTPField, Popover, PopoverClose, PopoverDescription, PopoverSheet, PopoverTitle, PopoverTrigger, ProgressiveBlur, Prose, Radio, RadioGroup, Select, Separator, Shimmer, Skeleton, Slider, Spinner, Stack, StaticNoise, Surface, Switch, Tabs, Text, Textarea, Textfield, Title, Toast, ToggleButton, ToggleGroup, ToggleTextButton, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Typewriter, getElevationProps, useBreakpoints, useDirection };
@@ -9,7 +9,8 @@
9
9
  <item>Actions that need visible text — use Button</item>
10
10
  <item>Missing both aria-label and aria-labelledby</item>
11
11
  <item>Navigation or non-button semantics — use IconButtonLink</item>
12
- <item>Temporally swap icons after user interactions with temporal pressed state, like copy feedback. Use ToggleButton instead</item>
12
+ <item>Temporally swap icons after user interactions with temporal pressed state, like copy feedback. Use ToggleButton (icon-only) or ToggleTextButton (labeled)</item>
13
+
13
14
  </when_not_to_use>
14
15
  <prop name="icon" kind="react">
15
16
  <description>Icon rendered as the sole visible control content. Omit glyph `size` / width / height — host CSS sizes SVG via `--vui-button-icon-size`.</description>
@@ -10,7 +10,8 @@
10
10
  <do>Icon-only navigation — use IconButtonLink (framework Link via `render`)</do>
11
11
  <dont>Passing `children`, `addon`, or `addonPosition` — not part of the public API; use `icon`</dont>
12
12
  <dont>Using icon-only Button instead of IconButton</dont>
13
- <dont>Using IconButton for persistent or temporal pressed state — use ToggleButton</dont>
13
+ <dont>Using IconButton for persistent or temporal pressed state — use ToggleButton (icon-only) or ToggleTextButton (labeled)</dont>
14
+
14
15
  <dont>Using IconButton with `render={&lt;a /&gt;}` for links — use IconButtonLink</dont>
15
16
  <dont>Omitting both `aria-label` and `aria-labelledby`</dont>
16
17
  <dont>Passing `size={16}` / `size=&quot;1rem&quot;` (or width/height) on the `icon` glyph — IconButton CSS sizes SVG descendants</dont>
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <meta id="prose" title="Prose" category="typography" shared_contract="prose">
3
- <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>
3
+ <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>
4
4
  <exports>
5
5
  <export name="Prose" />
6
6
  </exports>
@@ -36,7 +36,7 @@
36
36
  </Prose>
37
37
  </Stack>
38
38
  ]]></tsx>
39
- <notes>Text-wall path: Prose for rhythm + Stack measure sized for body `size` (here `s` → narrower than `m`). Not for short card blurbs — those stay Text/Title props (`maxWidth` / `balanced`).</notes>
39
+ <notes>Text-wall path: Prose for rhythm + Stack measure sized for body `size` (here `s` → narrower than `m`). Not for short card blurbs — those stay Text/Title props (`maxWidth` / `wrap`).</notes>
40
40
  </example>
41
41
 
42
42
  <example title="Density and region align">
@@ -3,12 +3,12 @@
3
3
  <when_to_use>
4
4
  <item>Text walls — articles, docs, privacy/terms, long editorial — whose direct children are headings and blocks that need heading-aware vertical rhythm</item>
5
5
  <item>Article, docs, or changelog regions where Stack `rowGap` would make heading→body and body→body the same</item>
6
- <item>Optional `gap` density (`s` / `m` / `l`) and region `align` (Title/Text that omit `align` inherit it)</item>
6
+ <item>Optional `gap` density (`s` / `m` / `l`), region `align` (Title/Text that omit `align` inherit it), and region `wrap` (default `pretty`; inherits to descendants)</item>
7
7
  <item>Reading column via parent Stack `maxWidth` sized for the body Text `size` (smaller → narrower; not one fixed `65ch`–`75ch`); edge-to-edge wall of text is not OK</item>
8
8
  </when_to_use>
9
9
  <when_not_to_use>
10
10
  <item>UI chrome spacing (cards, toolbars, form rows) — use Stack `rowGap` / `columnGap`</item>
11
- <item>Short card/hero blurbs “for leading” — use Text/Title `lineHeight` / `maxWidth` / `balanced`</item>
11
+ <item>Short card/hero blurbs “for leading” — use Text/Title `lineHeight` / `maxWidth` / `wrap` instead</item>
12
12
  <item>Restyling type, lists, links, or tables — stay on Title / Text / consumer CSS</item>
13
13
  <item>Markdown parse or sanitize — Prose only spaces matching direct children</item>
14
14
  </when_not_to_use>
@@ -18,6 +18,9 @@
18
18
  <prop name="gap" kind="react">
19
19
  <description>Em density for heading-aware vertical rhythm (`s` &lt; `m` &lt; `l`).</description>
20
20
  </prop>
21
+ <prop name="wrap" kind="react">
22
+ <description>Controls CSS `text-wrap` on the Prose root (inherits to descendants unless a child overrides). Default `pretty` for orphan-aware article walls. Prefer `balance` only on short multi-line blocks that need even line lengths. Single-line metrics stay on child Text/Title `whiteSpace`.</description>
23
+ </prop>
21
24
  <prop name="ref" kind="react">
22
25
  <description>Ref forwarded to the rendered prose root element.</description>
23
26
  </prop>
@@ -36,4 +39,7 @@
36
39
  <prop name="gap-heading-start" kind="css">
37
40
  <description>Gap after a heading onto the next sibling (`--vui-prose-gap-heading-start`). Default `1.3em` (`l`).</description>
38
41
  </prop>
42
+ <prop name="wrap" kind="css">
43
+ <description>Text-wrap on the Prose root (`--vui-prose-wrap`; inherits). Prefer the `wrap` React prop; default `pretty`.</description>
44
+ </prop>
39
45
  </props>