Forms & Inputs
Button
A clickable button (or link styled as a button) with variants, sizes, loading and icon support.
Preview
Section titled “Preview”<nana-button variant="primary">Save changes</nana-button> Installation
Section titled “Installation”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 buttonLands 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.
npx @nana-tec/ui add buttonLands as src/lib/components/ui/button/Button.svelte, 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.
Install the package for a versioned dependency:
npm install @nana-tec/ui-componentsOr copy the source in and own it:
npx @nana-tec/ui add buttonimport { 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.
<script lang="ts"> import { Button } from '$lib/components/ui/button';</script>
<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 use snippets:
<Button loading loadingText="Saving">Save</Button>
<Button variant="ghost" aria-label="Search"> {#snippet prefix()}<SearchIcon />{/snippet}</Button>Every native attribute passes through via ...rest, and href renders an
anchor.
import "@nana-tec/ui-components/button";<nana-button variant="primary">Save changes</nana-button><nana-button variant="ghost" size="sm">Cancel</nana-button><nana-button href="/docs" variant="link">Read the docs</nana-button>Loading state and slotted icons:
<nana-button loading loading-text="Saving">Save</nana-button>
<nana-button variant="ghost" aria-label="Search"> <svg slot="prefix"></svg></nana-button>Attributes are kebab-case (loading-text, full-width); the matching
properties are camelCase in JavaScript.
Styling with Tailwind
Section titled “Styling with Tailwind”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.
Variants
Section titled “Variants”<nana-button variant="primary">Primary</nana-button>
<nana-button variant="secondary">Secondary</nana-button>
<nana-button variant="tertiary">Tertiary</nana-button>
<nana-button variant="danger">Danger</nana-button>
<nana-button variant="success">Success</nana-button>
<nana-button variant="ghost">Ghost</nana-button>
<nana-button variant="link">Link</nana-button> <nana-button size="xs">XS</nana-button>
<nana-button size="sm">SM</nana-button>
<nana-button size="md">MD</nana-button>
<nana-button size="lg">LG</nana-button>
<nana-button size="xl">XL</nana-button> States
Section titled “States”<nana-button>Default</nana-button>
<nana-button disabled>Disabled</nana-button>
<nana-button loading>Loading</nana-button> The three implementations share one API. Only the spelling differs, following each framework’s conventions:
| React | Svelte | Web Components | |
|---|---|---|---|
| Variant | variant="primary" | variant="primary" | variant="primary" |
| Loading label | loadingText | loadingText | loading-text |
| Full width | fullWidth | fullWidth | full-width |
| Leading icon | prefix={<Icon />} | {#snippet prefix()} | slot="prefix" |
| Click | onClick | onclick | addEventListener("click") |
| As a link | href | href | href |
| Extra classes | className | class | class 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
| Prop | Type |
|---|---|
href | undefined |
Also accepts CommonOmit<ButtonHTMLAttributes<HTMLButtonElement>, 'prefix' | 'children'>.
ButtonLink
| Prop | Type |
|---|---|
hrefrequired | string |
Also accepts CommonOmit<AnchorHTMLAttributes<HTMLAnchorElement>, 'prefix' | 'children'>.
Button
| Prop | Type | Default |
|---|---|---|
variant | ButtonVariant | 'primary' |
size | Size | 'md' |
shape | ButtonShape | 'default' |
loading | boolean | false |
loadingText | string | |
fullWidth | boolean | false |
href | string | |
children | Snippet | |
prefix | Snippet | |
suffix | Snippet |
Also accepts Omit<HTMLButtonAttributes, 'prefix' | 'class'>Omit<HTMLAnchorAttributes, 'prefix' | 'class'>.
The attribute names, events and CSS hooks below are the Web Components reference.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
active | active | boolean | false | Pressed / current state (visual only — not a variant). |
ariaLabel | aria-label | string | null | null | |
as | as | ButtonAs | 'button' | Polymorphic host element. href implies a automatically. |
disabled | disabled | boolean | false | |
form | form | string | undefined | — | |
fullWidth | full-width | boolean | false | |
href | href | string | undefined | — | |
iconOnly | icon-only | boolean | false | |
loading | loading | boolean | false | |
loadingText | loading-text | string | undefined | — | |
name | name | string | undefined | — | |
rel | rel | string | undefined | — | |
rounded | rounded | 'none' | 'sm' | 'md' | 'lg' | 'full' | 'md' | |
shape | shape | ButtonShape | 'default' | Layout shape (radius system). Overrides rounded when not default. |
size | size | Size | 'md' | |
target | target | '_blank' | '_self' | '_parent' | '_top' | undefined | — | |
type | type | 'button' | 'submit' | 'reset' | 'button' | |
value | value | string | undefined | — | |
variant | variant | ButtonVariant | 'primary' |
Slots
| Slot | Description |
|---|---|
(default) | Button label |
prefix | Content before the label (e.g. a leading icon) |
suffix | Content after the label (e.g. a trailing icon) |
CSS parts
| Part | Description |
|---|---|
::part(base) | The underlying <button> or <a> element |
::part(loader) | The spinner shown while loading |
CSS custom properties
| Property | Description |
|---|---|
--nana-button-bg | Background colour (hover derives from it automatically) |
--nana-button-color | Text colour |
--nana-button-radius | Corner radius (overrides the rounded attribute) |
Events
Section titled “Events”| Event | Type |
|---|---|
nana-click | CustomEvent |
| Slot | Description |
|---|---|
(default) | Button label |
prefix | Content before the label (e.g. a leading icon) |
suffix | Content after the label (e.g. a trailing icon) |
CSS Variables
Section titled “CSS Variables”| Variable | Description |
|---|---|
--nana-button-bg | Background colour (hover derives from it automatically) |
--nana-button-color | Text colour |
--nana-button-radius | Corner radius (overrides the rounded attribute) |
CSS Parts
Section titled “CSS Parts”| Part | Description |
|---|---|
base | The underlying <button> or <a> element |
loader | The spinner shown while loading |
Accessibility
Section titled “Accessibility”- Keyboard accessible
- Screen-reader labels
- Focus-visible states
- ARIA roles and states
- WCAG 2.2 AA contrast