Button component for interface actions. Renders as a regular link when href is provided. Buttons should primarily be used to trigger events or actions.
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>
);<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>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>
);<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>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>
);<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>import { Button } from "@kesko/components";
export default () => (
<Button variant="primary" onPress={() => alert("Pressed!")}>
Press me
</Button>
);<button class="k-button k-button-primary" onclick="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>
);<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>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>
);<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>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>
);<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>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>
);<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>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>
);<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>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>
);<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>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>
);<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
| Name | Type | Default | Description |
|---|---|---|---|
variant | "default" | "primary" | "secondary" | "plain" | "text" | "inverted" | "default" | The style variant of the button. |
size | "sm" | "md" | "lg" | "md" | The size of the button. |
expand | boolean | false | Makes the button fit its container. |
transparent | boolean | false | Removes the background from the default and plain variants. |
disabled | boolean | false | Disables the component. Native |
loading | boolean | false | Shows loading state with spinner. |
aria-label | string | — | Accessible name. Required for icon-only buttons. |
aria-labelledby | string | — |
|
aria-describedby | string | — |
|
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 |
routerOptions | RouterOptions | — | Options forwarded to the registered router's |
Events
| Name | Type | Description |
|---|---|---|
onPress | (event: PressEvent) => void | Handler called on press (mouse, touch, or keyboard). Replaces |
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.
| Name | Description | Default |
|---|---|---|
--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) |