Button

ui actionform

Polymorpher Button mit Varianten (default, primary, ghost, danger) und Größen (default, square für Icon-Buttons). forwardRef, reicht alle nativen <button>-Attribute durch. aria-pressed=true rendert den aktiven Toggle-Zustand.

Abhängigkeiten

Barrierefreiheit

  • Echtes <button>-Element — Tastatur/Fokus/Disabled kommen nativ.
  • Sichtbarer :focus-visible-Ring (--focus-ring).
  • Toggle-Zustand über aria-pressed="true" stylebar.

Beispiele

Varianten
<Button>Default</Button>
<Button variant="primary">Speichern</Button>
<Button variant="ghost">Abbrechen</Button>
<Button variant="danger">Löschen</Button>
Icon-Button (square)
<Button size="square" aria-label="Einstellungen">
  <SettingsIcon />
</Button>

Genutzte Tokens · 17

--accent--accent-active--accent-hover--accent-on--bg--border--control-height--danger--ease-standard--fg--field-pad-x--focus-ring--motion-fast--radius-sm--space-2--surface--text-sm

Quelldateien

components/ui/Button.tsx tsx
import { forwardRef, type ButtonHTMLAttributes } from 'react';
import styles from './Button.module.css';

type Variant = 'default' | 'primary' | 'ghost' | 'danger';
type Size = 'default' | 'square';

interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: Variant;
  size?: Size;
}

export const Button = forwardRef<HTMLButtonElement, Props>(function Button(
  { variant = 'default', size = 'default', className, children, ...rest },
  ref,
) {
  const cls = [styles.btn, styles[variant], size === 'square' ? styles.square : '', className]
    .filter(Boolean)
    .join(' ');
  return (
    <button ref={ref} className={cls} {...rest}>
      {children}
    </button>
  );
});
components/ui/Button.module.css css
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: var(--control-height);
  padding: 0 var(--field-pad-x);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1;
  box-sizing: border-box;
  transition:
    background-color var(--motion-fast) var(--ease-standard),
    border-color var(--motion-fast) var(--ease-standard),
    transform var(--motion-fast) var(--ease-standard);
}
.btn:hover {
  border-color: color-mix(in oklab, var(--border), var(--fg) 25%);
  background: var(--bg);
}
.btn:active {
  transform: translateY(1px);
}
.btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.btn[aria-pressed='true'] {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
}
.btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.btn:disabled:hover {
  background: var(--surface);
  border-color: var(--border);
}
.primary:disabled:hover {
  background: var(--accent);
  border-color: var(--accent);
}

.btn :global(svg) {
  flex-shrink: 0;
}

.primary {
  background: var(--accent);
  color: var(--accent-on);
  border-color: var(--accent);
}
.primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}
.primary:active {
  background: var(--accent-active);
}

.danger {
  background: var(--danger);
  color: #fff;
  border-color: var(--danger);
}
.danger:hover {
  background: color-mix(in oklab, var(--danger), black 12%);
  border-color: color-mix(in oklab, var(--danger), black 12%);
}
.danger:active {
  background: color-mix(in oklab, var(--danger), black 20%);
}
.danger:disabled:hover {
  background: var(--danger);
  border-color: var(--danger);
}

.ghost {
  border-color: transparent;
  background: transparent;
}
.ghost:hover {
  background: var(--bg);
  border-color: var(--border);
}

.square {
  width: var(--control-height);
  padding: 0;
}

Holen

# MCP (Claude Code / Cursor)
get_component({ name: "button" })

# Registry direkt
https://www.ekklesi.tools/design/registry/components/button.json