---
title: "Button"
description: "Token-variant buttons as button, link or div, with ButtonTitle and ButtonIcon."
requested_language: nl
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/nl/components/button
version: unreleased (main)
docs_index: https://docs.systhema.app/nl/llms.txt
---
> This page isn't translated yet. Showing English.


`Button` renders an action or a link in one of the button variants defined by your tokens. It is polymorphic: the same styles apply to a `<button>`, a link or any other element, and `ButtonTitle` and `ButtonIcon` lay out the label and an optional icon.

```tsx preview title="Button"
import { Button, ButtonIcon, ButtonTitle } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Button.a href="#examples" variant="primary">
        <ButtonTitle>Get started</ButtonTitle>
        <ButtonIcon />
      </Button.a>
      <Button.a href="#variants-and-tags" variant="secondary">
        <ButtonTitle>See variants</ButtonTitle>
      </Button.a>
    </div>
  )
}
```

## Import

```tsx
import { Button, ButtonIcon, ButtonTitle } from '@systhemaui/next'
```

In a React app without Next.js, import them from `@systhemaui/react`. There `Button.a` renders a plain anchor with the same hash handling.

## Variants and tags

### Variants

`variant` takes one of the button variants in your tokens: `primary` and `secondary` with the default tokens. Each variant has its own colors for the normal and hover states, its own padding, radius, border, shadow, font and icon size. Without `variant`, the first variant in the tokens is used.

```tsx preview title="Variants"
import { Button } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Button.button variant="primary">Primary</Button.button>
      <Button.button variant="secondary">Secondary</Button.button>
    </div>
  )
}
```

A variant you add in Figma becomes a new `button-<name>` class and a new member of the `ButtonVariant` type after the next sync.

### Tags

| Component       | Renders                                                                    |
| --------------- | -------------------------------------------------------------------------- |
| `Button`        | a `<button>` by default, or the element in `as`                            |
| `Button.button` | a `<button>`                                                               |
| `Button.a`      | a link through the [`LinkHelper`](https://docs.systhema.app/nl/components/utilities.md#linkhelper) (`next/link`) |
| `Button.div`    | a `<div>`, for a button look inside another interactive element            |

```tsx preview title="Tags"
import { Button } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Button.button type="button">Button.button</Button.button>
      <Button.a href="#tags">Button.a</Button.a>
      <Button.div>Button.div</Button.div>
    </div>
  )
}
```

Use `Button.button` (or `Button` with `type`) for actions and `Button.a` for navigation. `Button.div` is not focusable and has no role; use it only where an outer element, such as a card link, is the real control.

## Examples

### With an icon

An empty `ButtonIcon` draws the default arrow, sized by the variant's icon token and colored with the text. Put it before `ButtonTitle` for a leading icon. `ButtonTitle` grows to fill the button, which keeps the icon at the edge in a wide button.

```tsx preview title="Default icon, leading and trailing"
import { Button, ButtonIcon, ButtonTitle } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Button.a href="#with-an-icon" variant="primary">
        <ButtonTitle>Continue</ButtonTitle>
        <ButtonIcon />
      </Button.a>
      <Button.a href="#with-an-icon" variant="secondary">
        <ButtonIcon />
        <ButtonTitle>Leading icon</ButtonTitle>
      </Button.a>
      <Button.a href="#with-an-icon" variant="primary" className="w-64">
        <ButtonTitle>Full row</ButtonTitle>
        <ButtonIcon />
      </Button.a>
    </div>
  )
}
```

Change the default arrow for the whole project with `blocks.button.icon` (see [Component CSS blocks](https://docs.systhema.app/nl/styling/blocks.md)).

### Custom icon

`ButtonIcon.<tag>` renders the icon as that element. `ButtonIcon.svg` makes the SVG itself the icon, so it takes the variant's icon size and color.

```tsx preview title="Custom icon"
import { Button, ButtonIcon, ButtonTitle } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Button.button variant="primary">
        <ButtonIcon.svg viewBox="0 -960 960 960" fill="currentColor">
          <path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z" />
        </ButtonIcon.svg>
        <ButtonTitle>Download</ButtonTitle>
      </Button.button>
      <Button.button variant="secondary">
        <ButtonTitle>Favorite</ButtonTitle>
        <ButtonIcon.svg viewBox="0 -960 960 960" fill="currentColor">
          <path d="m480-120-58-52q-101-91-167-157T150-447.5Q111-500 95.5-544T80-634q0-94 63-157t157-63q52 0 99 22t81 62q34-40 81-62t99-22q94 0 157 63t63 157q0 46-15.5 90T810-447.5Q771-395 705-329T538-172l-58 52Z" />
        </ButtonIcon.svg>
      </Button.button>
    </div>
  )
}
```

### Icon-only

An icon-only button needs one accessible name. Keep the `ButtonTitle` and hide it visually with `sr-only!`, or set `aria-label` on the button, never both.

```tsx preview title="Icon-only"
import { Button, ButtonIcon, ButtonTitle } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Button.button variant="primary">
        <ButtonTitle className="sr-only!">Next step</ButtonTitle>
        <ButtonIcon />
      </Button.button>
      <Button.button variant="secondary" aria-label="Next step">
        <ButtonIcon />
      </Button.button>
    </div>
  )
}
```

### Disabled

A disabled button keeps its normal colors, ignores hover and shows the default cursor.

```tsx preview title="Disabled"
import { Button, ButtonIcon, ButtonTitle } from '@systhemaui/next'

export default function Demo() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Button.button variant="primary" disabled>
        <ButtonTitle>Saving…</ButtonTitle>
      </Button.button>
      <Button.button variant="secondary" disabled>
        <ButtonTitle>Continue</ButtonTitle>
        <ButtonIcon />
      </Button.button>
    </div>
  )
}
```

The tokens have no separate disabled colors. Add your own, for example `disabled:opacity-50`, when the state needs to read more clearly.

### Form submit

`Button` passes every attribute to the element, so `type`, `form`, `name` and `value` work as on a native button.

```tsx preview title="Submit button"
import { Button, ButtonTitle } from '@systhemaui/next'

export default function Demo() {
  return (
    <form className="flex flex-wrap items-center gap-4" action="#form-submit">
      <Button.button type="submit" variant="primary">
        <ButtonTitle>Subscribe</ButtonTitle>
      </Button.button>
      <Button.button type="reset" variant="secondary">
        <ButtonTitle>Reset</ButtonTitle>
      </Button.button>
    </form>
  )
}
```

## Props

### `Button`

`Button` and its shorthands take the props of the element they render. `variant` is typed from your tokens.

<!-- generated:props @systhemaui/react ButtonProps -->

| Prop                           | Type                                     | Default    | Description |
| ------------------------------ | ---------------------------------------- | ---------- | ----------- |
| `as`                           | `ElementType`                            | `'button'` |             |
| `variant`                      | `ButtonVariant` (`primary`, `secondary`) | -          |             |
| `className`                    | `string`                                 | -          |             |
| `children`                     | `ReactNode`                              | -          |             |
| …and all `<button>` attributes |                                          |            |             |

<!-- /generated -->

### `ButtonIcon`

`ButtonIcon` renders a `<span>` by default and always sets `aria-hidden="true"` (pass `aria-hidden={false}` to override). An empty `ButtonIcon` shows the configured default icon. `ButtonTitle` renders a `<span>` and takes its attributes.

<!-- generated:props @systhemaui/react ButtonIconProps -->

| Prop                        | Type          | Default | Description |
| --------------------------- | ------------- | ------- | ----------- |
| `as`                        | `ElementType` | `'svg'` |             |
| `className`                 | `string`      | -       |             |
| …and all `<svg>` attributes |               |         |             |

<!-- /generated -->

## HTML and CSS

Without React, put the variant class on any element. One class per variant is generated (`button-primary` and `button-secondary` with the default tokens), and an empty `.button-icon` element draws the default icon:

```html
<a class="button-primary" href="/signup">
  <span class="button-title">Get started</span>
  <span class="button-icon" aria-hidden="true"></span>
</a>

<button class="button-secondary" type="button">
  <span class="button-title">See variants</span>
</button>
```

The component also adds the `transitionClasses` from your config; in plain HTML add transition utilities yourself. A project `transition` token collection sets the duration and easing per variant (see [Motion tokens](https://docs.systhema.app/nl/design/motion-tokens.md)).

<!-- generated:utilities button -->

| Class              | Styles                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `button-primary`   | `& { --tw-shadow-color: var(--color-button-primary-normal-shadow); --tw-shadow: var(--tw-shadow-color); vertical-align: bottom; position: relative; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; border-style: solid; border-radius: var(--button-primary-border-radius); border-width: var(--button-primary-border-width); box-shadow: var(--button-primary-shadow-x) var(--button-primary-shadow-y) var(--button-primary-shadow-blur) var(--button-pri…`<br>+ 9 more rules |
| `button-secondary` | `& { --tw-shadow-color: var(--color-button-secondary-normal-shadow); --tw-shadow: var(--tw-shadow-color); vertical-align: bottom; position: relative; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; border-style: solid; border-radius: var(--button-secondary-border-radius); border-width: var(--button-secondary-border-width); box-shadow: var(--button-secondary-shadow-x) var(--button-secondary-shadow-y) var(--button-secondary-shadow-blur) var(…`<br>+ 9 more rules |

<!-- /generated -->

## Next.js

The `@systhemaui/next` `Button.a` renders through the Next-aware [`LinkHelper`](https://docs.systhema.app/nl/components/utilities.md#linkhelper): internal routes navigate on the client and prefetch, and same-page hashes scroll smoothly. Passing `as="a"` to `Button` takes the same route, not a bare `<a>`.

```tsx
import { Button, ButtonIcon, ButtonTitle } from '@systhemaui/next'

export function Cta() {
  return (
    <Button.a href="/contact" variant="primary">
      <ButtonTitle>Talk to us</ButtonTitle>
      <ButtonIcon />
    </Button.a>
  )
}
```

`Button` is not a `'use client'` module, so its shorthands also work when you import it in a Server Component.

## Accessibility

- `ButtonIcon` renders with `aria-hidden="true"`, so assistive technology announces only the title.
- An icon-only button gets exactly one name: a visually hidden `ButtonTitle` (`sr-only!`) or an `aria-label`. Two names make some screen readers read the label twice.
- Use a real `<button>` for actions and a link for navigation, so keyboard and screen-reader behavior match what the control does.

See [Icons and icon-only controls](https://docs.systhema.app/nl/concepts/accessibility.md#icons-and-icon-only-controls).

## Related

- [Button block](https://docs.systhema.app/nl/payload/blocks/button.md)
- [Link](https://docs.systhema.app/nl/components/link.md)
- [Chip](https://docs.systhema.app/nl/components/chip.md)
- [Component CSS blocks](https://docs.systhema.app/nl/styling/blocks.md)
