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

Open in new tab
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>
);
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>
);
import { Button } from "@kesko/components";

export default () => (
  <Button variant="primary" onPress={() => alert("Pressed!")}>
    Press me
  </Button>
);
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>
);
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>
);
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>
);
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>
);
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>
);
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>
);
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>
);

Props

NameTypeDefaultDescription
variant"default" | "primary" | "secondary" | "plain" | "text" | "inverted""default"

The style variant of the button.

size"sm" | "md" | "lg""md"

The size of the button.

expandbooleanfalse

Makes the button fit its container.

transparentbooleanfalse

Removes the background from the default and plain variants.

disabledbooleanfalse

Disables the component. Native disabled on buttons, aria-disabled on links.

loadingbooleanfalse

Shows loading state with spinner.

aria-labelstring

Accessible name. Required for icon-only buttons.

aria-labelledbystring

id of an element that labels this button.

aria-describedbystring

id of an element that describes this button.

hrefstring

Renders as an anchor element.

target"_self" | "_blank" | "_parent" | "_top"

Where to open the linked URL.

downloadboolean | string

Triggers a file download. Pass true to use the server filename or a string to override it.

routerOptionsRouterOptions

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

NameTypeDescription
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

CSS custom properties provide finer-grained control than props. Prefer the props API when it suits your need.

NameDescriptionDefault
--k-button-bg

Overrides the background color.

var(--k-color-bg)
--k-button-text

Overrides the text color.

var(--k-color-text-link)
--k-button-border

Overrides the border color.

var(--k-color-text-link)
--k-button-radius

Overrides the border radius.

var(--k-radius-button)
--k-button-focus

Overrides the focus ring color.

var(--k-color-border-focus)
--k-button-bg-hover

Overrides the hover background color.

var(--k-button-bg)
--k-button-bg-active

Overrides the pressed background color.

var(--k-button-bg)
--k-button-text-hover

Overrides the hover text color.

oklch(from var(--k-button-text) calc(l + 0.13) calc(c + 0.03) h)
--k-button-text-active

Overrides the pressed text color.

oklch(from var(--k-button-text) calc(l + 0.24) c h)
--k-button-ring-hover

Overrides the grow-ring color on hover.

oklch(from var(--k-button-border) calc(l + 0.13) calc(c + 0.03) h)
--k-button-ring-active

Overrides the grow-ring color when pressed.

oklch(from var(--k-button-border) calc(l + 0.24) c h)
--k-button-height

Overrides the button height at the active size.

calc(var(--k-font-leading-sm) + var(--k-space-button) * 2 + var(--k-size-border-button) * 2)