
# Button



> Button component for interface actions. Renders as a regular link when href is provided. Buttons should primarily be used to trigger events or actions.

## Examples

### Default

```tsx
import { Button, Stack } from "@kesko/components";
import { IconBasketAddTo, IconExternalLink } from "@kesko/iconography/react";

export default () => (
  <Stack direction="row" gap="2xs" align="center" wrap>
    <Button variant="primary">
      <IconBasketAddTo /> Primary
    </Button>
    <Button variant="primary">
      <IconBasketAddTo label="Add to basket" />
    </Button>
    <Button>
      Default <IconExternalLink />
    </Button>
    <Button disabled>Disabled</Button>
    <Button variant="plain">Plain</Button>
    <Button variant="text">Text</Button>
  </Stack>
);

```

```html
<div class="k-stack k-stack-row k-stack-gap-2xs k-stack-align-center k-stack-wrap">
  <button class="k-button k-button-primary">
    <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M17.5 14.748a.75.75 0 0 1 .75.75v2.25h2.25a.75.75 0 0 1 0 1.5h-2.25v2.25a.75.75 0 0 1-1.5 0v-2.25H14.5a.75.75 0 0 1 0-1.5h2.25v-2.25a.75.75 0 0 1 .75-.75zM12 2.25a5.252 5.252 0 0 1 5.251 5.252v.746a.756.756 0 0 1-.012.136.75.75 0 0 1-.739.883L2.961 9.25 5.54 19.559a.25.25 0 0 0 .242.19h7.407a.75.75 0 0 1 0 1.5H5.78a1.75 1.75 0 0 1-1.698-1.326L1.273 8.68A.75.75 0 0 1 2 7.748l4.749.006v-.256A5.248 5.248 0 0 1 12 2.25zm7.006 5.498l3 .02a.75.75 0 0 1 .723.932l-1.625 6.487a.75.75 0 0 1-1.455-.364l1.393-5.562-2.045-.013a.75.75 0 1 1 .01-1.5zM12 3.75a3.749 3.749 0 0 0-3.75 3.748v.258l7.5.01v-.264A3.752 3.752 0 0 0 12 3.75z"
        fill="currentColor"
      />
    </svg>
    Primary
  </button>
  <button class="k-button">Default</button>
  <button class="k-button" disabled>Disabled</button>
  <button class="k-button k-button-plain">Plain</button>
  <button class="k-button k-button-text">Text</button>
</div>
```

### As link

```tsx
import { Button, Stack } from "@kesko/components";
import { IconExternalLink, IconDownload } from "@kesko/iconography/react";

export default () => (
  <Stack direction="row" gap="2xs">
    <Button href="https://kesko.design" variant="primary" target="_blank">
      Visit website <IconExternalLink />
    </Button>
    <Button href="/file.pdf" download>
      Download PDF <IconDownload />
    </Button>
  </Stack>
);

```

```html
<div class="k-stack k-stack-row k-stack-gap-2xs">
  <a class="k-button k-button-primary" href="https://kesko.design" target="_blank" rel="noopener">
    Visit website
    <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M10.5 5.75a.75.75 0 0 1 0 1.5H3.25v11.5h17.5V11.5a.75.75 0 0 1 1.5 0v8a.75.75 0 0 1-.75.75h-19a.75.75 0 0 1-.75-.75v-13a.75.75 0 0 1 .75-.75h8zm7.47-2.78a.75.75 0 0 1 1.06 0l3 3a.75.75 0 0 1 0 1.06l-3 3a.75.75 0 1 1-1.06-1.06l1.719-1.72H18.5a5.75 5.75 0 0 0-5.746 5.53l-.004.22a.75.75 0 0 1-1.5 0 7.25 7.25 0 0 1 7.25-7.25h1.189l-1.72-1.72a.75.75 0 0 1-.072-.976l.073-.084z"
        fill="currentColor"
      />
    </svg>
  </a>
  <a class="k-button" href="/file.pdf" download>
    Download PDF
    <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M15 3.75a5.75 5.75 0 0 1 5.75 5.699A5.75 5.75 0 0 1 18 20.25h-4.5a.75.75 0 0 1 0-1.5H18a4.25 4.25 0 0 0 1.686-8.153.75.75 0 0 1-.45-.749 4.25 4.25 0 0 0-7.92-2.469.75.75 0 0 1-1.067.25A2.25 2.25 0 0 0 6.75 9.5a.75.75 0 0 1-.749.749 4.25 4.25 0 0 0 0 8.5h4.5a.75.75 0 0 1 0 1.5H6a5.75 5.75 0 0 1-.683-11.46 3.751 3.751 0 0 1 5.1-2.763A5.744 5.744 0 0 1 15 3.75zm-3 6a.75.75 0 0 1 .75.75v4.19l1.22-1.22a.75.75 0 1 1 1.06 1.06l-2.5 2.5-.008.009a.814.814 0 0 1-.046.041l.054-.05a.791.791 0 0 1-.084.073c-.25.192-.608.24-.914-.016a.778.778 0 0 1-.088-.081L8.97 14.53c-.707-.707.353-1.767 1.06-1.06l1.22 1.219V10.5a.75.75 0 0 1 .75-.75z"
        fill="currentColor"
      />
    </svg>
  </a>
</div>
```

### Custom style

```tsx
import type { CSSProperties } from "react";
import { Button, Stack } from "@kesko/components";

export default () => (
  <Stack direction="row" gap="2xs" align="center">
    <Button
      variant="primary"
      style={
        {
          "--k-button-bg": "var(--k-color-status-error)",
          "--k-button-text": "var(--k-color-text-on-error)",
          "--k-button-border": "var(--k-color-status-error)",
        } as CSSProperties
      }
    >
      Delete account
    </Button>
    <Button
      variant="primary"
      style={
        {
          "--k-button-bg": "var(--k-color-status-success)",
          "--k-button-text": "var(--k-color-text-on-success)",
          "--k-button-border": "var(--k-color-status-success)",
        } as CSSProperties
      }
    >
      Confirm order
    </Button>
  </Stack>
);

```

```html
<div class="k-stack k-stack-row k-stack-gap-2xs k-stack-align-center">
  <button
    class="k-button k-button-primary"
    style="
      --k-button-bg: var(--k-color-status-error);
      --k-button-text: var(--k-color-text-on-error);
      --k-button-border: var(--k-color-status-error);
    "
  >
    Delete account
  </button>
  <button
    class="k-button k-button-primary"
    style="
      --k-button-bg: var(--k-color-status-success);
      --k-button-text: var(--k-color-text-on-success);
      --k-button-border: var(--k-color-status-success);
    "
  >
    Confirm order
  </button>
</div>
```

### Events

```tsx
import { Button } from "@kesko/components";

export default () => (
  <Button variant="primary" onPress={() => alert("Pressed!")}>
    Press me
  </Button>
);

```

```html
<button class="k-button k-button-primary" onclick="alert('Pressed!')">Press me</button>
```

### Expand (full width)

```tsx
import { Button, Stack } from "@kesko/components";
import { IconArrowUp } from "@kesko/iconography/react";

export default () => (
  <Stack direction="row" gap="2xs" align="center">
    <Button variant="primary" expand>
      Full width
    </Button>
    <Button variant="primary" expand>
      Back to top <IconArrowUp />
    </Button>
  </Stack>
);

```

```html
<div class="k-stack k-stack-row k-stack-gap-2xs k-stack-align-center">
  <button class="k-button k-button-primary k-button-expand">Full width</button>
  <button class="k-button k-button-primary k-button-expand">
    Back to top
    <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M11.432 1.508a.755.755 0 0 1 .357-.227.736.736 0 0 1 .741.19l9.5 9.5a.75.75 0 0 1-1.06 1.06l-8.22-8.22v18.19a.75.75 0 0 1-1.5 0V3.81l-8.22 8.22a.75.75 0 1 1-1.06-1.06l9.462-9.463z"
        fill="currentColor"
      />
    </svg>
  </button>
</div>
```

### Inverted

```tsx
import { Fragment } from "react";
import { Button, Stack } from "@kesko/components";
import { IconBasketAddTo } from "@kesko/iconography/react";

const sizes = [
  { size: "sm" as const, label: "Small" },
  { size: "md" as const, label: "Medium" },
  { size: "lg" as const, label: "Large" },
];

export default () => (
  <Stack
    direction="row"
    gap="2xs"
    align="center"
    wrap
    style={{
      padding: "0.5rem",
      background: "#000",
      color: "var(--k-color-text-on-accent)",
      borderRadius: "0.5rem",
    }}
  >
    {sizes.map(s => (
      <Fragment key={s.size}>
        <Button variant="inverted" size={s.size}>
          <IconBasketAddTo /> {s.label}
        </Button>
        <Button aria-label={`Inverted ${s.label}`} variant="inverted" size={s.size}>
          <IconBasketAddTo />
        </Button>
      </Fragment>
    ))}
  </Stack>
);

```

```html
<div
  class="k-stack k-stack-row k-stack-gap-2xs k-stack-align-center k-stack-wrap"
  style="padding: 0.5rem; background: #000; border-radius: 0.5rem"
>
  <button class="k-button k-button-inverted">Inverted</button>
  <button class="k-button k-button-inverted k-button-sm">Small inverted</button>
  <button class="k-button k-button-inverted k-button-lg">Large inverted</button>
</div>
```

### Loading state

```tsx
import { Button, Stack } from "@kesko/components";

export default () => (
  <Stack direction="row" gap="2xs" align="center" wrap>
    <Button variant="primary" loading>
      Loading
    </Button>
    <Button loading>Loading</Button>
    <Button loading variant="plain">
      Loading
    </Button>
  </Stack>
);

```

```html
<div class="k-stack k-stack-row k-stack-gap-2xs k-stack-align-center k-stack-wrap">
  <button class="k-button k-button-primary" data-loading>
    Loading
    <span class="k-spinner" role="status" aria-label="Loading">
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
    </span>
  </button>
  <button class="k-button" data-loading>
    Loading
    <span class="k-spinner" role="status" aria-label="Loading">
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
    </span>
  </button>
  <button class="k-button k-button-plain" data-loading>
    Loading
    <span class="k-spinner" role="status" aria-label="Loading">
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
    </span>
  </button>
</div>
```

### Sizes

```tsx
import { Fragment } from "react";
import { Button, Stack } from "@kesko/components";
import { IconBasketAddTo } from "@kesko/iconography/react";

const variants = [
  { key: "primary", label: "Primary", props: { variant: "primary" as const } },
  { key: "secondary", label: "Secondary", props: { variant: "secondary" as const } },
  { key: "default", label: "Default", props: {} },
  { key: "plain", label: "Plain", props: { variant: "plain" as const } },
  { key: "text", label: "Text", props: { variant: "text" as const } },
];

const sizes = [
  { size: "sm" as const, label: "Small" },
  { size: "md" as const, label: "Medium" },
  { size: "lg" as const, label: "Large" },
];

export default () => (
  <Stack gap="2xs">
    {variants.map(row => (
      <Stack key={row.key} direction="row" gap="2xs" align="center" wrap>
        <strong style={{ fontSize: "0.875rem", inlineSize: "6rem" }}>{row.label}</strong>
        {sizes.map(s => (
          <Fragment key={s.size}>
            <Button {...row.props} size={s.size}>
              <IconBasketAddTo /> {s.label}
            </Button>
            <Button aria-label={`${row.label} ${s.label}`} {...row.props} size={s.size}>
              <IconBasketAddTo />
            </Button>
          </Fragment>
        ))}
      </Stack>
    ))}
  </Stack>
);

```

```html
<div class="k-stack k-stack-row k-stack-gap-2xs k-stack-align-center k-stack-wrap">
  <button class="k-button k-button-primary k-button-sm">Small</button>
  <button class="k-button k-button-primary">Medium</button>
  <button class="k-button k-button-primary k-button-lg">Large</button>
</div>
```

### Sizes (loading)

```tsx
import { Button, Stack } from "@kesko/components";

const variants = [
  { key: "primary", label: "Primary", props: { variant: "primary" as const } },
  { key: "secondary", label: "Secondary", props: { variant: "secondary" as const } },
  { key: "default", label: "Default", props: {} },
  { key: "plain", label: "Plain", props: { variant: "plain" as const } },
  { key: "text", label: "Text", props: { variant: "text" as const } },
];

const sizes = ["sm", "md", "lg"] as const;

export default () => (
  <Stack gap="2xs">
    {variants.map(row => (
      <Stack key={row.key} direction="row" gap="2xs" align="center" wrap>
        <strong style={{ fontSize: "0.875rem", inlineSize: "6rem" }}>{row.label}</strong>
        {sizes.map(size => (
          <Button key={size} {...row.props} size={size} loading>
            Loading
          </Button>
        ))}
      </Stack>
    ))}
  </Stack>
);

```

```html
<div class="k-stack k-stack-row k-stack-gap-2xs k-stack-align-center k-stack-wrap">
  <button class="k-button k-button-primary k-button-sm" data-loading>
    Loading
    <span class="k-spinner k-spinner-sm" role="status" aria-label="Loading">
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
    </span>
  </button>
  <button class="k-button k-button-primary" data-loading>
    Loading
    <span class="k-spinner" role="status" aria-label="Loading">
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
    </span>
  </button>
  <button class="k-button k-button-primary k-button-lg" data-loading>
    Loading
    <span class="k-spinner k-spinner-lg" role="status" aria-label="Loading">
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
      <span aria-hidden="true"></span>
    </span>
  </button>
</div>
```

### Transparent

```tsx
import { Fragment } from "react";
import { Button, Stack } from "@kesko/components";
import { IconBasketAddTo } from "@kesko/iconography/react";

const sizes = [
  { size: "sm" as const, label: "Small" },
  { size: "md" as const, label: "Medium" },
  { size: "lg" as const, label: "Large" },
];

export default () => (
  <Stack
    direction="row"
    gap="2xs"
    align="center"
    wrap
    style={{ padding: "0.5rem", background: "var(--k-color-bg-hover)", borderRadius: "0.5rem" }}
  >
    {sizes.map(s => (
      <Fragment key={s.size}>
        <Button transparent size={s.size}>
          <IconBasketAddTo /> {s.label}
        </Button>
        <Button aria-label={`Transparent ${s.label}`} transparent size={s.size}>
          <IconBasketAddTo />
        </Button>
      </Fragment>
    ))}
  </Stack>
);

```

```html
<div
  class="k-stack k-stack-row k-stack-gap-2xs k-stack-align-center k-stack-wrap"
  style="padding: 0.5rem; background: var(--k-color-bg-hover); border-radius: 0.5rem"
>
  <button class="k-button k-button-transparent">Default transparent</button>
  <button class="k-button k-button-plain k-button-transparent">Plain transparent</button>
</div>
```

### Variants

```tsx
import { Button, Stack } from "@kesko/components";
import { IconBasketAddTo } from "@kesko/iconography/react";

export default () => (
  <Stack direction="row" gap="2xs" align="center" wrap>
    <Button variant="primary">
      <IconBasketAddTo /> Primary
    </Button>
    <Button variant="primary" aria-label="Add to basket">
      <IconBasketAddTo />
    </Button>
    <Button variant="secondary">
      <IconBasketAddTo /> Secondary
    </Button>
    <Button>Default</Button>
    <Button disabled>Disabled</Button>
    <Button variant="plain">Plain</Button>
    <Button variant="text">Text</Button>
  </Stack>
);

```

```html
<div class="k-stack k-stack-row k-stack-gap-2xs k-stack-align-center k-stack-wrap">
  <button class="k-button k-button-primary">
    <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M17.5 14.748a.75.75 0 0 1 .75.75v2.25h2.25a.75.75 0 0 1 0 1.5h-2.25v2.25a.75.75 0 0 1-1.5 0v-2.25H14.5a.75.75 0 0 1 0-1.5h2.25v-2.25a.75.75 0 0 1 .75-.75zM12 2.25a5.252 5.252 0 0 1 5.251 5.252v.746a.756.756 0 0 1-.012.136.75.75 0 0 1-.739.883L2.961 9.25 5.54 19.559a.25.25 0 0 0 .242.19h7.407a.75.75 0 0 1 0 1.5H5.78a1.75 1.75 0 0 1-1.698-1.326L1.273 8.68A.75.75 0 0 1 2 7.748l4.749.006v-.256A5.248 5.248 0 0 1 12 2.25zm7.006 5.498l3 .02a.75.75 0 0 1 .723.932l-1.625 6.487a.75.75 0 0 1-1.455-.364l1.393-5.562-2.045-.013a.75.75 0 1 1 .01-1.5zM12 3.75a3.749 3.749 0 0 0-3.75 3.748v.258l7.5.01v-.264A3.752 3.752 0 0 0 12 3.75z"
        fill="currentColor"
      />
    </svg>
    Primary
  </button>
  <button class="k-button k-button-primary" aria-label="Add to basket">
    <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M17.5 14.748a.75.75 0 0 1 .75.75v2.25h2.25a.75.75 0 0 1 0 1.5h-2.25v2.25a.75.75 0 0 1-1.5 0v-2.25H14.5a.75.75 0 0 1 0-1.5h2.25v-2.25a.75.75 0 0 1 .75-.75zM12 2.25a5.252 5.252 0 0 1 5.251 5.252v.746a.756.756 0 0 1-.012.136.75.75 0 0 1-.739.883L2.961 9.25 5.54 19.559a.25.25 0 0 0 .242.19h7.407a.75.75 0 0 1 0 1.5H5.78a1.75 1.75 0 0 1-1.698-1.326L1.273 8.68A.75.75 0 0 1 2 7.748l4.749.006v-.256A5.248 5.248 0 0 1 12 2.25zm7.006 5.498l3 .02a.75.75 0 0 1 .723.932l-1.625 6.487a.75.75 0 0 1-1.455-.364l1.393-5.562-2.045-.013a.75.75 0 1 1 .01-1.5zM12 3.75a3.749 3.749 0 0 0-3.75 3.748v.258l7.5.01v-.264A3.752 3.752 0 0 0 12 3.75z"
        fill="currentColor"
      />
    </svg>
  </button>
  <button class="k-button k-button-secondary">
    <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="M17.5 14.748a.75.75 0 0 1 .75.75v2.25h2.25a.75.75 0 0 1 0 1.5h-2.25v2.25a.75.75 0 0 1-1.5 0v-2.25H14.5a.75.75 0 0 1 0-1.5h2.25v-2.25a.75.75 0 0 1 .75-.75zM12 2.25a5.252 5.252 0 0 1 5.251 5.252v.746a.756.756 0 0 1-.012.136.75.75 0 0 1-.739.883L2.961 9.25 5.54 19.559a.25.25 0 0 0 .242.19h7.407a.75.75 0 0 1 0 1.5H5.78a1.75 1.75 0 0 1-1.698-1.326L1.273 8.68A.75.75 0 0 1 2 7.748l4.749.006v-.256A5.248 5.248 0 0 1 12 2.25zm7.006 5.498l3 .02a.75.75 0 0 1 .723.932l-1.625 6.487a.75.75 0 0 1-1.455-.364l1.393-5.562-2.045-.013a.75.75 0 1 1 .01-1.5zM12 3.75a3.749 3.749 0 0 0-3.75 3.748v.258l7.5.01v-.264A3.752 3.752 0 0 0 12 3.75z"
        fill="currentColor"
      />
    </svg>
    Secondary
  </button>
  <button class="k-button">Default</button>
  <button class="k-button" disabled>Disabled</button>
  <button class="k-button k-button-plain">Plain</button>
  <button class="k-button k-button-text">Text</button>
</div>
```


## Props

- `variant` — `"default" | "primary" | "secondary" | "plain" | "text" | "inverted"` (default: `"default"`) The style variant of the button.
- `size` — `"sm" | "md" | "lg"` (default: `"md"`) The size of the button.
- `expand` — `boolean` (default: `false`) Makes the button fit its container.
- `transparent` — `boolean` (default: `false`) Removes the background from the default and plain variants.
- `disabled` — `boolean` (default: `false`) Disables the component. Native `disabled` on buttons, `aria-disabled` on links.
- `loading` — `boolean` (default: `false`) Shows loading state with spinner.
- `aria-label` — `string` Accessible name. Required for icon-only buttons.
- `aria-labelledby` — `string` `id` of an element that labels this button.
- `aria-describedby` — `string` `id` of an element that describes this button.
- `href` — `string` Renders as an anchor element.
- `target` — `"_self" | "_blank" | "_parent" | "_top"` Where to open the linked URL.
- `download` — `boolean | string` Triggers a file download. Pass `true` to use the server filename or a string to override it.
- `routerOptions` — `RouterOptions` Options forwarded to the registered router's `navigate` function when clicked. Only effective inside a react-aria `RouterProvider`. Shape depends on your router (e.g. Next's `{ scroll: false }`).

## Events

- `onPress` — `(event: PressEvent) => void` Handler called on press (mouse, touch, or keyboard). Replaces `onClick`.
- `onPressStart` — `(event: PressEvent) => void` Handler called when a press interaction starts.
- `onPressEnd` — `(event: PressEvent) => void` Handler called when a press interaction ends (success or cancel).
- `onPressUp` — `(event: PressEvent) => void` Handler called when a press is released over the target.
- `onPressChange` — `(isPressed: boolean) => void` Handler called when the press interaction state changes.
- `onFocus` — `(event: FocusEvent<ButtonTarget>) => void` Handler called when the element receives focus.
- `onBlur` — `(event: FocusEvent<ButtonTarget>) => void` Handler called when the element loses focus.

## CSS custom properties

- `--k-button-bg` (default: `var(--k-color-bg)`) Overrides the background color.
- `--k-button-text` (default: `var(--k-color-text-link)`) Overrides the text color.
- `--k-button-border` (default: `var(--k-color-text-link)`) Overrides the border color.
- `--k-button-radius` (default: `var(--k-radius-button)`) Overrides the border radius.
- `--k-button-focus` (default: `var(--k-color-border-focus)`) Overrides the focus ring color.
- `--k-button-bg-hover` (default: `var(--k-button-bg)`) Overrides the hover background color.
- `--k-button-bg-active` (default: `var(--k-button-bg)`) Overrides the pressed background color.
- `--k-button-text-hover` (default: `oklch(from var(--k-button-text) calc(l + 0.13) calc(c + 0.03) h)`) Overrides the hover text color.
- `--k-button-text-active` (default: `oklch(from var(--k-button-text) calc(l + 0.24) c h)`) Overrides the pressed text color.
- `--k-button-ring-hover` (default: `oklch(from var(--k-button-border) calc(l + 0.13) calc(c + 0.03) h)`) Overrides the grow-ring color on hover.
- `--k-button-ring-active` (default: `oklch(from var(--k-button-border) calc(l + 0.24) c h)`) Overrides the grow-ring color when pressed.
- `--k-button-height` (default: `calc(var(--k-font-leading-sm) + var(--k-space-button) * 2 + var(--k-size-border-button) * 2)`) Overrides the button height at the active size.
