Skip to main content
KvirnUIPre-alpha

Button

A button that does something, such as sending a form or saving a draft. It never sends a form by accident, and it can stay reachable by keyboard when it’s disabled.

Status: Alpha

Automated tests and an independent accessibility review pass. Manual testing with assistive technology is pending. Don't use it in a live service yet.

Example

Primary, secondary and danger buttons

Knappar som tar bort något behöver alltid ett bekräftelsesteg. De visas tillsammans här bara för att kunna jämföras.

Code

import { Button } from '@kvirn-ui/react'
import '@kvirn-ui/theme/theme.css'

<div className="kv-button-group">
  <Button className="kv-button--primary">Send application</Button>
  <Button>Save draft</Button>
  <Button className="kv-button--danger">Delete draft</Button>
</div>

When to use it

  • Use a button when the user does something: sends, saves, deletes or opens.
  • Write the label as a verb that says what will happen: “Send application”, not “OK”.
  • Use one primary button per page, for the main next step.
  • Deleting and other actions that can’t be undone need a confirmation step.

When not to use it

To go to another page, use a Link. Don’t make a link look like a button, or a button look like a link.

Installation

pnpm add @kvirn-ui/react @kvirn-ui/i18n

For the default look, also add @kvirn-ui/theme and import theme.css once, for example in your root layout. Every Button is then styled. Remove the import, and it’s unstyled again: KvirnUI never loads CSS for you.

pnpm add @kvirn-ui/theme

import '@kvirn-ui/theme/theme.css'

Usage

A button that does something

type="button" is the default, so a Button never sends a form by accident.

<Button onClick={saveDraft}>Save draft</Button>

Sending a form

Use type="submit" for the one button that sends the form.

<form onSubmit={sendApplication}>
  {/* fields */}
  <Button type="submit" className="kv-button--primary">
    Send application
  </Button>
</form>

Disabled buttons

Try not to disable buttons. Let people press them and then explain what’s missing. If you must disable one, use focusableWhenDisabled so keyboard and screen reader users can still find it, and show the reason next to it.

Disabled with a reason

Fyll i alla obligatoriska fält innan du skickar.

Code

<p id="send-reason">Fill in all required fields before you send.</p>
<Button
  className="kv-button--primary"
  disabled
  focusableWhenDisabled
  aria-describedby="send-reason"
>
  Send application
</Button>

Using your own button component

Use render to change the element. It must still render a <button>. To go somewhere, use Link.

<Button render={<MyStyledButton />}>Save draft</Button>

The hook: useButton

Build your own button with the same behaviour. Pass your click handler to the hook, so it stays blocked while the button is disabled.

const button = useButton({ disabled: isSaving, focusableWhenDisabled: true, onClick: save })

<button {...button.buttonProps}>Save draft</button>

Styling

Button renders class="kv-button", and its state as data-disabled and data-focus-visible. The default theme styles that class and those attributes. Choose how much of it you want.

1. Use the default theme

Import @kvirn-ui/theme/theme.css. A Button is secondary by default. Add className="kv-button--primary" for the one main action on the page, or className="kv-button--danger" for an action that deletes something. Put class="kv-compact" on a container for smaller buttons in staff tools.

<Button className="kv-button--primary">Send application</Button>

2. Override variables

The theme is role scales (--kv-primary-500, --kv-neutral-50), named for what they do and not for their hue, and semantic tokens that point at them (--kv-color-primary). Set either in your own CSS. Everything in theme.css is in @layer kv, so your CSS always wins.

/* Rebrand: give the primary scale your brand's colours. All four themes follow. */
:root {
  --kv-primary-50: #edfafa;
  --kv-primary-100: #cdf0f0;
  --kv-primary-200: #9be0e2;
  --kv-primary-300: #5fc6cb;
  --kv-primary-400: #1e9ca4;
  --kv-primary-500: #007d86;
  --kv-primary-600: #00707a;
  --kv-primary-700: #005a62;
  --kv-primary-800: #00474e;
  --kv-primary-900: #003a40;
  --kv-primary-950: #00262a;
}

A secondary Button's edge uses the secondary scale, which is the neutral steps by default. Give --kv-secondary-* your own 11 steps to colour it.

Each theme uses different steps, so a swapped scale can break contrast. Check your colours with checkThemeCss() from @kvirn-ui/theme on your customised copy. It measures every text, control and focus pair in all four themes.

3. Replace it or skip it

theme.css is one readable file. Copy it from node_modules/@kvirn-ui/theme/theme.css into your project, edit it, and import your copy instead. Or skip it, and style the kv-button class and the state attributes with Tailwind or your own CSS.

<Button className="rounded-md border px-4 py-2 data-disabled:border-dashed data-focus-visible:outline-2">
  Save draft
</Button>