# Button

> A button for actions. CSS component with one class and no JavaScript.

The class goes on an ordinary `<button>`. No JavaScript is loaded, and it works the same in every framework.

## How to use it

**Plain HTML**

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/tokens/tokens.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/components/css/button/button.css">
<!-- Or everything in one file: https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/dist/lotek.css -->
```

```html
<button class="lt-button">Send application</button>
<button class="lt-button" data-variant="secondary">Save draft</button>
<button class="lt-button" data-variant="ghost">Add attachment</button>
<button class="lt-button" data-variant="danger">Delete application</button>
```

**TypeScript**

```ts
import "@lo-tek/interfaces/tokens.css"
import "@lo-tek/interfaces/button.css"
```

`lt` is used in two ways, and which one depends on whether you create the element or find it.

**If you create the markup,** you spread the attributes in. That is the route in React, Astro and anything else that renders:

```tsx
import { lt } from "@lo-tek/interfaces/react"


<button {...lt.button()}>Send application</button>
```

The import goes to `/react` here, since the block is JSX. From there the keys are called `className` and `htmlFor`. In Astro and anything else without React you import `@lo-tek/interfaces` and get `class` and `for`.

**If the element is already there,** you set them on it. That is the route when you enhance HTML a server or a template has sent:

```ts
import { lt } from "@lo-tek/interfaces"


const button = document.querySelector<HTMLButtonElement>("#send")


lt.setAttributes(button, lt.button({ variant: "secondary" }))
```

`lt.button()` without an argument gives the primary button, which needs no `data-variant`.

If the variant comes from an API or a URL, validate it:

```ts
import { lt, type ButtonVariant } from "@lo-tek/interfaces"


const variant: ButtonVariant = lt.button.isVariant(response.variant)
  ? response.variant
  : "primary"


lt.setAttributes(button, lt.button({ variant }))
```

See [Type-safe use](/type-safe-use.md).

**A reusable button in React.**

```tsx
import { lt } from "@lo-tek/interfaces/react"
import type { ButtonVariant } from "@lo-tek/interfaces"
import type { ComponentProps } from "react"


type Props = ComponentProps<"button"> & {
  variant?: ButtonVariant
}


export function Button({ variant, className, ...rest }: Props) {
  const button = lt.button({ variant })
  return (
    <button
      {...button}
      {...rest}
      className={className ? `${button.className} ${className}` : button.className}
    />
  )
}
```

`className` is merged with the system’s class. If it is only spread in after `lt.button()`, it replaces `lt-button`, and the button loses its style without anything telling you.

If the team changes framework, it is this one component that is rewritten, not the design system.

`ComponentProps<"button">` includes `ref`, which since React 19 is an ordinary prop and is passed through `{...rest}` without `forwardRef`. `ComponentPropsWithRef` and `ComponentPropsWithoutRef` are no longer needed.

| `data-variant` | Used for |
| - | - |
| *(omitted)* | The main action on the page. Use only one. |
| `secondary` | An alternative action next to the main action |
| `ghost` | Low weight, for toolbars, actions in tables and cards |
| `danger` | Deletes, or cannot be undone |

## Disabled

**Plain HTML**

```html
<button class="lt-button" disabled>Send application</button>
<button class="lt-button" data-variant="secondary" disabled>Save draft</button>
<a class="lt-button" role="link" tabindex="0" aria-disabled="true">Go to the overview</a>
```

`disabled` exists only on `<button>`. If the button is an `<a>`, use `aria-disabled="true"` and remove `href`, otherwise Enter follows it. Write `role="link"` and `tabindex="0"` alongside, so screen readers still find the link and can say that it is unavailable. See [Link](/components/link.md#disabled-link).

## A link that looks like a button

**Plain HTML**

```html
<a class="lt-button" href="#">Go to the overview</a>
<a class="lt-button" data-variant="secondary" href="#">Read the terms</a>
```

If you are navigating the user, the element should be an `<a>` even if it looks like a button. Then right-click, middle-click and “open in new tab” work.

## On a narrow screen

The button text wraps when it has to. A button with a long label, such as “Send the application for housing benefit for the whole of 2026”, would otherwise become wider than the phone, and then the whole page scrolls sideways.

If you need a full-width button, that is the app’s layout:

```css
.form-button {
  inline-size: 100%;
}
```

## Classes you can style

| Class | What it is |
| - | - |
| `.lt-button` | The button. The variant comes from `data-variant`. |

| Variable | Default |
| - | - |
| `--lt-button-padding` | `var(--lt-spacing-2) var(--lt-spacing-4)` |
| `--lt-button-radius` | `var(--lt-spacing-1)` |
| `--lt-button-border-width` | `1px` |
| `--lt-button-font-weight` | `var(--lt-font-weight-medium)` |
| `--lt-button-line-height` | `var(--lt-line-height-default)` |

```css
:root {
  --lt-button-radius: 9999px;
}
```

An example. A button in a table row needs less space than one standing on its own:

**Plain HTML**

```html
<button class="lt-button">Open the case</button>
<button class="lt-button row-button" data-variant="secondary">Open</button>
```

```css
.row-button {
--lt-button-padding: var(--lt-spacing-1) var(--lt-spacing-3);
--lt-button-radius: var(--lt-spacing-4);
}
```

The class `row-button` is yours. The system does not know it, and it works because the variable is inherited down into the button.

The default value follows the `--lt-spacing-*` scale, so the button stays in step with the rest when you rewrite the scale, step by step. See [Customising](/customising.md).

## Accessibility

- `<button>` performs an action, `<a>` takes you somewhere else. Choose by what happens, not by how it looks.
- Inside a `<form>`, `type="submit"` is the default. If the button should not submit the form, write `type="button"`.
- Let the text describe the action: “Delete user” says more than “OK”.
- Do not use a disabled button as the only explanation of why something cannot be done. Show an error message the user can act on.
