Button
The styled Base UI button: four variants, three sizes, and an unstyled escape hatch
import { Button } from "@nyte-ai/ui";Implemented in packages/ui/src/components/ui/button.tsx, wrapping
@nyte-ai/ui/button.
Usage
- Use
defaultfor the one primary action on a surface,outlinefor secondary actions beside it,ghostfor toolbar and inline actions, anddestructivefor actions that remove or discard. - Do not render a link as a Button. Base UI enforces button semantics; a link that looks like a
button is an
<a>styled by the app. typedefaults to"button"unlessrenderreplaces the element, so a Button inside a form never submits by accident. Saytype="submit"when you mean it.- An icon-only button (
size="icon-sm") must carryaria-label. - A button that becomes disabled while it holds focus (loading after click) should pass
focusableWhenDisabledso focus does not fall to<body>.
Anatomy
A single element. Button renders <button> with data-slot="button", data-variant, and
data-size.
<Button variant="outline" size="sm">
Label
</Button>Examples
Variants
<Button>Default</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>Sizes
<Button size="default">Default</Button>
<Button size="sm">Small</Button>
<Button size="icon-sm" aria-label="Add">
<IconPlusSmall size={14} />
</Button>Disabled
<Button disabled>Disabled</Button>
<Button variant="outline" disabled focusableWhenDisabled>
Focusable when disabled
</Button>Rendering as another element
Pass render. If the element is not a <button>, add nativeButton={false} so Base UI keeps
the keyboard and ARIA behavior. type is not set when render is present.
<Button render={<div />} nativeButton={false}>
Complex children
</Button>Unstyled
<Button unstyled xstyle={myStyles.trigger}>
Product-specific treatment
</Button>Props
ButtonProps is Base UI's Button.Props minus className/style, plus the styling channels and
appearance props. unstyled: true makes variant and size a type error.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "outline" | "ghost" | "destructive" | "default" | Fill and border treatment. |
size | "default" | "sm" | "icon-sm" | "default" | 28px, 24px, or 24×24 square with no horizontal padding. |
unstyled | boolean | false | Render with no StyleX class. Forbids variant and size. |
className | string | Consumer layout class, appended after the StyleX class. | |
style | React.CSSProperties | Inline style, merged after StyleX's. | |
xstyle | XStyle | StyleX override, applied last. | |
type | "button" | "submit" | "reset" | "button" | Defaulted only when render is absent. |
Inherited from Base UI Button
A button component that can be used to trigger actions.
Renders a <button> element.
Button Props:
| Prop | Type | Default | Description |
|---|---|---|---|
| focusableWhenDisabled | boolean | false | Whether the button should be focusable when disabled. |
| nativeButton | boolean | true | Whether the component renders a native <button> element when replacing it
via the render prop.
Set to false if the rendered element is not a button (for example, <div>). |
| className | string | ((state: Button.State) => string | undefined) | - | CSS class applied to the element, or a function that returns a class based on the component's state. |
| style | React.CSSProperties | ((state: Button.State) => React.CSSProperties | undefined) | - | Style applied to the element, or a function that returns a style object based on the component's state. |
| render | ReactElement | ((props: HTMLProps, state: Button.State) => ReactElement) | - | Allows you to replace the component's HTML element
with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render. |
Button Data Attributes:
| Attribute | Type | Description |
|---|---|---|
| data-disabled | - | Present when the button is disabled. |
Accessibility
Base UI's guarantees, which the wrapper does not alter:
- Renders a native
<button>; Enter and Space activate it. - With
renderto a non-button andnativeButton={false}, Base UI addsrole="button",tabIndex, and keyboard handlers so the element still behaves as a button. disabledsetsdata-disabledand, on a native button, thedisabledattribute.focusableWhenDisabledkeeps it in the tab order witharia-disabledinstead.- Focus ring:
:focus-visibleoutline in--nyte-color-focus-ring; see Focus.
Styling
| Token | Role |
|---|---|
--nyte-control-height-md / -sm | Height for default / sm and icon-sm |
--nyte-control-padding-sm / -xs | Horizontal padding for default / sm |
--nyte-radius-control | Corner radius |
--nyte-color-primary, -hover, -foreground | default variant |
--nyte-color-border, -border-strong, -muted | outline variant |
--nyte-color-muted-hover | ghost hover |
--nyte-color-destructive, -muted, -hover | destructive variant |
--nyte-control-disabled-opacity | Disabled opacity |
--nyte-motion-fast, --nyte-motion-ease-out | Color transitions; 0s under reduced motion |
State selectors used: :hover only under @media (hover: hover); [data-popup-open] mirrors
hover so a trigger stays lit while its menu is open; :active:not(:disabled) nudges 1px down.