{
  "title": "Button",
  "description": "Clickable action with variants, sizes, loading state, icon slots, and asChild.",
  "url": "/docs/components/button",
  "since": "0.1.0",
  "tags": [
    "control",
    "action"
  ],
  "platform": "both",
  "source": "---\ntitle: Button\ndescription: Clickable action with variants, sizes, loading state, icon slots, and asChild.\nsince: 0.1.0\ntags: [control, action]\nplatform: both\ncategory: controls\n---\n\nimport { BundleSize } from '@/components/bundle-size';\nimport { ExpoSnack } from '@/components/expo-snack';\nimport { Preview } from '@/components/preview';\nimport { PropsTable } from '@/components/props-table';\n\n<BundleSize component=\"Button\" />\n\n## At a glance\n\n- Click target with full keyboard / RN `Pressable` semantics — focus ring on web, press feedback on native.\n- Forwards every `Pressable` prop (`onPress`, `onLongPress`, `accessibilityLabel`, …) untouched.\n- Use [`Toggle`](/docs/components/toggle) for two-state buttons and [`FloatButton`](/docs/components/float-button) for the floating-action-button pattern.\n\n## Preview\n\n<Preview name=\"button-basic\" />\n\n## Native preview (Expo Snack)\n\n<ExpoSnack component=\"Button\" height={400} />\n\n## `variant`\n\nVisual emphasis. Pick by hierarchy, not color.\n\n| Value                | Use for                                                  |\n|----------------------|----------------------------------------------------------|\n| `primary` (default)  | The single most-important action on a screen             |\n| `secondary`          | Equally relevant actions, paired with a primary          |\n| `ghost`              | Tertiary actions in dense layouts (cards, toolbars)      |\n| `destructive`        | Irreversible actions — delete, leave, discard            |\n\n```tsx\n<Button variant=\"primary\">Save</Button>\n<Button variant=\"destructive\">Delete account</Button>\n```\n\n## `size`\n\nDensity. `md` is the default and matches the rest of the form\ncontrols; `sm` for dense toolbars, `lg` for hero CTAs.\n\n| Value          | Min height | Padding (horizontal) |\n|----------------|------------|----------------------|\n| `sm`           | 32 px      | 12 px                |\n| `md` (default) | 36 px      | 16 px                |\n| `lg`           | 44 px      | 20 px                |\n\n```tsx\n<Button size=\"lg\">Get started</Button>\n```\n\n## `loading` and `disabled`\n\n`loading` swaps the leading icon (or label, in icon-only buttons) for a\n[`Spinner`](/docs/components/spinner) and disables interaction. The\nbutton keeps its width so the layout doesn't jump when the request\nreturns. `disabled` greys the button out and blocks press handling\nwithout showing a spinner.\n\n```tsx\n<Button loading>Saving…</Button>\n<Button disabled>Save</Button>\n```\n\nBoth forward `aria-busy` / `aria-disabled` so screen readers narrate\nthe state correctly.\n\n## `leadingIcon` and `trailingIcon`\n\nTwo icon slots — pass any component matching\n`{ size?: number; color?: string }`. Sized automatically to match the\nbutton's `size`. The Button renders nothing for an empty slot, so a\ntext-only button stays text-only.\n\n```tsx\nimport { Save, ArrowRight } from 'lucide-react-native';\n\n<Button leadingIcon={Save}>Save changes</Button>\n<Button trailingIcon={ArrowRight}>Continue</Button>\n```\n\nIn `loading` state the `leadingIcon` is replaced by a Spinner; the\n`trailingIcon` stays so the right-edge layout doesn't jump.\n\n## `asChild`\n\nRenders the supplied child as the interactive root, so a Button can\nrender as a Next.js `<Link>` (or any other element) while keeping all\nof its styles and accessibility behavior. Common for full-card\nclickable areas and routed actions.\n\n```tsx\nimport { Button } from '@nori-ui/core';\nimport Link from 'next/link';\n\n<Button asChild variant=\"primary\">\n    <Link href=\"/billing\">Manage billing</Link>\n</Button>\n```\n\n## Props\n\n<PropsTable component=\"Button\" />\n"
}
