Skip to content
Nana UI

Forms & Inputs

Button

A clickable button (or link styled as a button) with variants, sizes, loading and icon support.

View .md Download
Save changes

The button ships for all three targets. Pick the tab for your project — the CLI detects which you are on and serves the matching implementation.

npx @nana-tec/ui add button

Lands as src/components/ui/button/button.tsx, with its variants, sizes and shapes in styles/button.ts beside it — that file is where you restyle it. Classes resolve to the design tokens. Requires Tailwind v4.

import { Button } from "@/components/ui/button";
export function Example() {
return (
<>
<Button variant="primary" onClick={() => save()}>Save changes</Button>
<Button variant="ghost" size="sm">Cancel</Button>
<Button href="/docs" variant="link">Read the docs</Button>
</>
);
}

Loading state and slotted icons:

<Button loading loadingText="Saving">Save</Button>
<Button prefix={<SearchIcon />} aria-label="Search" variant="ghost" />

It forwards a ref, and every native <button> attribute passes through. Giving it an href renders an <a> instead, with the right role and aria-disabled handling.

The React and Svelte buttons are written in Tailwind, using semantic class names that resolve to the design tokens. They live in styles/button.ts, which both frameworks read, so changing a variant changes it in both:

export const variants: Record<ButtonVariant, string> = {
primary: 'bg-primary text-primary-foreground hover:brightness-90',
};

Classes you pass are merged with tailwind-merge, so an override wins rather than fighting:

<Button variant="primary" className="rounded-none px-8">Save</Button>

See Using Tailwind for mapping the design tokens into Tailwind’s theme, or replacing the styles outright.

Primary Secondary Tertiary Danger Success Ghost Link
XS SM MD LG XL
Default Disabled Loading

The three implementations share one API. Only the spelling differs, following each framework’s conventions:

ReactSvelteWeb Components
Variantvariant="primary"variant="primary"variant="primary"
Loading labelloadingTextloadingTextloading-text
Full widthfullWidthfullWidthfull-width
Leading iconprefix={<Icon />}{#snippet prefix()}slot="prefix"
ClickonClickonclickaddEventListener("click")
As a linkhrefhrefhref
Extra classesclassNameclassclass on the host

React and Svelte merge the class you pass with their own, so utilities and one-off overrides compose rather than replace. Every other native attribute passes straight through to the underlying <button> or <a>.

The table below is generated from the Web Components build, and is the full reference for attribute names and CSS hooks.

The React and Svelte props, read from the components’ own types:

Button

PropType
hrefundefined

Also accepts CommonOmit<ButtonHTMLAttributes<HTMLButtonElement>, 'prefix' | 'children'>.

PropType
hrefrequiredstring

Also accepts CommonOmit<AnchorHTMLAttributes<HTMLAnchorElement>, 'prefix' | 'children'>.

The attribute names, events and CSS hooks below are the Web Components reference.

Properties

PropertyAttributeTypeDefaultDescription
activeactivebooleanfalsePressed / current state (visual only — not a variant).
ariaLabelaria-labelstring | nullnull
asasButtonAs'button'Polymorphic host element. href implies a automatically.
disableddisabledbooleanfalse
formformstring | undefined—
fullWidthfull-widthbooleanfalse
hrefhrefstring | undefined—
iconOnlyicon-onlybooleanfalse
loadingloadingbooleanfalse
loadingTextloading-textstring | undefined—
namenamestring | undefined—
relrelstring | undefined—
roundedrounded'none' | 'sm' | 'md' | 'lg' | 'full''md'
shapeshapeButtonShape'default'Layout shape (radius system). Overrides rounded when not default.
sizesizeSize'md'
targettarget'_blank' | '_self' | '_parent' | '_top' | undefined—
typetype'button' | 'submit' | 'reset''button'
valuevaluestring | undefined—
variantvariantButtonVariant'primary'

Slots

SlotDescription
(default)Button label
prefixContent before the label (e.g. a leading icon)
suffixContent after the label (e.g. a trailing icon)

CSS parts

PartDescription
::part(base)The underlying <button> or <a> element
::part(loader)The spinner shown while loading

CSS custom properties

PropertyDescription
--nana-button-bgBackground colour (hover derives from it automatically)
--nana-button-colorText colour
--nana-button-radiusCorner radius (overrides the rounded attribute)
EventType
nana-clickCustomEvent
SlotDescription
(default)Button label
prefixContent before the label (e.g. a leading icon)
suffixContent after the label (e.g. a trailing icon)
VariableDescription
--nana-button-bgBackground colour (hover derives from it automatically)
--nana-button-colorText colour
--nana-button-radiusCorner radius (overrides the rounded attribute)
PartDescription
baseThe underlying <button> or <a> element
loaderThe spinner shown while loading
  • Keyboard accessible
  • Screen-reader labels
  • Focus-visible states
  • ARIA roles and states
  • WCAG 2.2 AA contrast
Explore Button interactively Every state, prop, and edge case — with live controls — in Storybook.
View in Storybook