{"slug":"button","name":"Button","packageName":"@liquidify/react","category":"controls","maturity":"experimental","summary":"A user-invokable control that performs an immediate action.","importPath":"@liquidify/react/button","sourcePath":"packages/@liquidify/react/src/components/button/button.tsx","specPath":"packages/@liquidify/react/src/components/button/spec.md","docsPath":"../liquidify-page/content/component-source/button.md","documentationStatus":"draft","storyIds":["controls-button--default"],"additionalExports":["ButtonAsChildProps","ButtonRole","ButtonSize","ButtonVariant","LiquidColor","SizedControlProps","TintableControlProps"],"anatomy":[{"name":"Glass surface (the four-layer engine: highlight, refraction, tint, shadow)","description":"Glass surface (the four-layer engine: highlight, refraction, tint, shadow)"},{"name":"Content slot","description":"Content slot — a single normalized wrapper (.lq-button-content, an"},{"name":"Optional loading spinner (replaces interaction, not the content slot)","description":"Optional loading spinner (replaces interaction, not the content slot)"},{"name":"Focus ring (a CSS outline with an outline-offset transparent gap)","description":"Focus ring (a CSS outline with an outline-offset transparent gap)"}],"props":[{"name":"aria-label","type":"string | undefined","required":false,"description":"Accessible name — **REQUIRED** for an icon-only button (no visible text). Sets the native `aria-label` attribute on the rendered element. Alternative to `aria-labelledby` for labelling buttons that have no visible text.","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"aria-labelledby","type":"string | undefined","required":false,"description":"Accessible name by reference — alternative to `aria-label`. Sets the native `aria-labelledby` attribute on the rendered element. Points to the `id` of another element that describes the button. Useful when the label is already in the DOM and should be reused.","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"asChild","type":"false | undefined","required":false,"description":"Discriminant (charter §4 rule 3): `false` or omitted renders the native `<button>` arm ({@link ButtonProps}). Set `true` to switch to the {@link ButtonAsChildProps} arm instead — merges the glass surface onto the single element child (D4 / Slot composition) rather than rendering its own root, so a Button can become a link or wrap another library's element while keeping the glass look.","defaultValue":"`false`","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"children","type":"ReactNode","required":false,"description":"The visible label and accessible name for the button. Optional. Single line of text (or icon + text via `leadingIcon` / `trailingIcon` slots). children role: `label` (charter §8) — token parity (see spec.md § Tokens); Button exposes no `labelProps` seam, unlike most `TextLabel`-backed components (ADR-0028 rule 3). When {@link SlottableProps.asChildasChild} is set, this must be the single element to merge the glass surface onto (e.g. an `<a>`), not a text node.","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"className","type":"string | undefined","required":false,"description":"Optional additional CSS class merged into the glass surface class. The supported per-instance customisation seam: override any `--lq-*` custom property from this class's CSS — the token cascade the engine reads (ADR-0004). No raw inline style is involved, so this stays ADR-0003 clean.","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"disabled","type":"boolean | undefined","required":false,"description":"When `true`, sets the native `disabled` attribute on the `<button>` element. The button is removed from the tab order and all interaction (`onClick` / `onPress`) is suppressed. `aria-disabled` is NOT set — the native attribute provides full-suppression semantics per the WAI-ARIA Button pattern (Shape N, `docs/api-conventions.md` §3; see spec.md § Accessibility). With {@link SlottableProps.asChildasChild} the native attribute is unavailable on a non-form child (e.g. an `<a>`), so Button hard-suppresses instead (Shape A, P0.B4): activation is cancelled (`preventDefault`, no navigation, no `onClick` / `onPress` — the Slot's `defaultPrevented` cancellation also silences the child's own handler) and the root carries `aria-disabled=\"true\"`, STAYING focusable. In every mode the root is stamped with `data-disabled` for the CSS fallback (`.lq-button[data-disabled]`).","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"leadingIcon","type":"ReactNode","required":false,"description":"Optional leading glyph rendered before the label (e.g. an `<Icon>`). Pairs with `trailingIcon` to frame the label on left and right. When present alongside no visible text (icon-only mode), an accessible name **must** be supplied via {@link ButtonProps. \"aria-label\" | aria-label} or {@link ButtonProps. \"aria-labelledby\" | aria-labelledby} (or an associated `<label>`). An icon-only button with no accessible name is not perceivable to screen-reader users and violates WCAG 2.1 SC 1.1.1 (ADR-0019).","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"loading","type":"boolean | undefined","required":false,"description":"When `true`, the button is in a loading state: `aria-busy=\"true\"` is set, a spinner indicator is rendered, and `onClick` / `onPress` are suppressed. The button remains focusable while loading (unlike `disabled`).","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"onClick","type":"((event: MouseEvent<HTMLButtonElement>) => void) | undefined","required":false,"description":"Low-level native passthrough; prefer `onPress`. Fires FIRST, before `onPress`. Suppressed automatically when `disabled` or `loading` is `true`; consumers do not need to guard against those states themselves. Typed against the native `<button>` element (`asChild` false/omitted — see {@link ButtonAsChildProps.onClick} for the `asChild` arm, whose event stays polymorphic).","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"onPress","type":"(() => void) | undefined","required":false,"description":"Primary activation callback — SwiftUI `Button(action:)`. Payload-free and semantic: fires at most once per interaction (`docs/api-conventions.md` §1 rule 1). Suppressed automatically when `disabled` or `loading` is `true` — a suppressed interaction emits nothing. Fires AFTER {@link ButtonProps.onClickonClick}; if `onClick` calls `event.preventDefault()`, `onPress` is suppressed for that interaction — and so is the internal glass `data-pop` stamp on the `glass` / `glassProminent` variants (§1 rule 3).","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"ref","type":"Ref<HTMLButtonElement> | undefined","required":false,"description":"Forwarded ref to the native `<button>` — the charter-documented ref target for Button's native arm (`docs/api-conventions.md` §4).","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"role","type":"ButtonRole | undefined","required":false,"description":"The button's {@link ButtonRolerole} — its purpose, **orthogonal** to {@link ButtonProps.variantvariant} (SwiftUI `Button(role:)`). Surfaced as `data-role` (a styling hook only — never the DOM `role` attribute, which would clobber the implicit `button` role). Omit for a neutral button. - `destructive` — irreversible / data-loss action; red label (red fill on the prominent variants). - `cancel` — dismisses without committing; rendered like a neutral button.","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"size","type":"ButtonSize | undefined","required":false,"description":"Size of the button — the `small · regular · large` subset of the canonical {@link SizedControlProps.sizesize scale}. Resolved to geometry via `data-size` attribute selectors in `styles.css`, which compose min-height and padding from the shared `--lq-space-*` scale and the label from `--lq-font-size-*` (see `docs/components/button/token-vocabulary.md`).","defaultValue":"`\"regular\"`","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"tint","type":"LiquidTintValue | undefined","required":false,"description":"System-accent of the control — drawn from the RESTRICTED {@link LIQUID_COLORS} palette (the thirteen Apple system colours). **Orthogonal to a component's `variant`**: it recolours the accent the surface draws from (focus ring, tinted fill, selection) without changing its variant. Resolved via `data-tint` attribute selectors in CSS (tokens-only, ADR-0003).","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"trailingIcon","type":"ReactNode","required":false,"description":"Optional trailing glyph rendered after the label. Pairs with `leadingIcon` to frame the label on left and right. When present alongside no visible text (icon-only mode), an accessible name **must** be supplied via {@link ButtonProps. \"aria-label\" | aria-label} or {@link ButtonProps. \"aria-labelledby\" | aria-labelledby} (or an associated `<label>`). An icon-only button with no accessible name is not perceivable to screen-reader users and violates WCAG 2.1 SC 1.1.1 (ADR-0019).","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"},{"name":"variant","type":"ButtonVariant | undefined","required":false,"description":"Visual variant of the button surface — the SwiftUI `buttonStyle`, surfaced as the `data-variant` attribute and resolved to chrome + label colour in CSS. - `automatic` — context default; text-only, system-blue label, no capsule. - `plain` — text-only, no capsule; label is the **default label colour** (black in light / white in dark). - `bordered` — frosted Liquid-Glass capsule that refracts the backdrop; system-blue label. - `borderedProminent` — solid systemBlue fill, white label. - `borderless` — text-only, no capsule; system-blue label. - `glass` — live Liquid-Glass capsule refracting the backdrop; blue label. - `glassProminent` — solid tint fill with a prominent Liquid-Glass specular edge. - `translucent` — no SwiftUI `buttonStyle` analogue (see spec.md § Variants); a tint-accent capsule mixed toward transparent so the engine glass refracts through it, with a matching accent label.","defaultValue":"`\"automatic\"`","declaredIn":"packages/@liquidify/react/src/components/button/button.tsx"}],"swiftUIMap":[{"swiftUI":"`Button(action:) { label }`","react":"`children`","notes":"children role: `label`, token parity (see Tokens). `action` is payload-free, mirrored by `onPress` below.","relationship":"direct"},{"swiftUI":"`action: () -> Void`","react":"`onPress`","notes":"The primary activation convention (charter §1) — payload-free, fires at most once per interaction.","relationship":"direct"},{"swiftUI":"*(no SwiftUI analogue — DOM passthrough)*","react":"`onClick`","notes":"Low-level native passthrough; prefer `onPress`. Fires first; `event.preventDefault()` here suppresses `onPress` (and, in `asChild` mode, the child's own handler via Slot cancellation, P0.B4).","relationship":"web-only"},{"swiftUI":"`.buttonStyle(_:)`","react":"`variant`","notes":"`ButtonVariant`, 1:1 with Apple's `ButtonStyle` values; `translucent` is the one style with no direct Apple analogue (see above).","relationship":"direct"},{"swiftUI":"`Button(role:)`","react":"`role`","notes":"Orthogonal to `variant`; recolours the label (and prominent fill) only.","relationship":"direct"},{"swiftUI":"`.controlSize(_:)`","react":"`size`","notes":"`small | regular | large` — the canonical 3-step button-shaped-control subset (charter §7).","relationship":"direct"},{"swiftUI":"`.tint(_:)`","react":"`tint`","notes":"Restricted `LiquidColor` (13 system colours); orthogonal to `variant`.","relationship":"direct"},{"swiftUI":"`.disabled(_:)`","react":"`disabled`","notes":"Shape N (native `<button disabled>`, no `aria-disabled`) by default; Shape A (`aria-disabled` + `data-disabled` + hard suppression, stays focusable) when `asChild` (charter §3).","relationship":"direct"},{"swiftUI":"*(web-only, no SwiftUI analogue)*","react":"`loading`","notes":"`aria-busy=\"true\"` + spinner; suppresses activation like `disabled` but stays focusable.","relationship":"web-only"},{"swiftUI":"*(web-only, no SwiftUI analogue)*","react":"`className`","notes":"Inherited surface axis (`LiquidBaseProps`) — merges into the glass class.","relationship":"web-only"},{"swiftUI":"*(web-only — D4 Slot composition)*","react":"`asChild`","notes":"Discriminant: `false`/omitted renders `<button>`; `true` merges the surface onto the single element child instead (e.g. an `<a>`).","relationship":"web-only"},{"swiftUI":"*(no SwiftUI analogue — refs)*","react":"`ref`","notes":"Typed to the charter-declared target: `Ref<HTMLButtonElement>` when `asChild` is `false`/omitted (native `<button>`); the child's own element type via Slot merge when `asChild` is `true` (charter §4).","relationship":"web-only"},{"swiftUI":"`Label(_:systemImage:)`","react":"`leadingIcon` / `trailingIcon`","notes":"Icon placement either side of the label; icon-only mode requires `aria-label`/`aria-labelledby` (ADR-0019).","relationship":"direct"},{"swiftUI":"*(web-only, accessibility)*","react":"`aria-label` / `aria-labelledby`","notes":"Accessible-name overrides for icon-only Buttons.","relationship":"web-only"}],"tokens":["--lq-button-fill","--lq-button-label","--lq-button-ring","--lq-color-accent","--lq-color-action-primary","--lq-color-label-primary-","--lq-color-on-action-primary","--lq-color-system-blue-","--lq-color-system-red-","--lq-duration-fast","--lq-duration-snappy","--lq-easing-standard","--lq-font-size-","--lq-font-weight-medium","--lq-glass-","--lq-glass-fill-0","--lq-hit-target-","--lq-line-height-","--lq-motion-hover-scale","--lq-motion-press-clarity","--lq-motion-press-duration","--lq-motion-press-scale","--lq-radius-10","--lq-radius-14","--lq-space-"],"keyboard":[{"key":"Keyboard","action":"Space / Enter activates (fires the same onClick → onPress sequence via the native click semantics); Tab focuses; arrow keys do nothing."}],"variants":["automatic","bordered","borderedProminent","borderless","glass","glassProminent","plain","translucent"],"variantPages":[]}