Zum Inhalt springen
UI Kit
Accent

Tasten

Tasten und Kürzel, die auf Apple-Geräten Befehl und sonst Strg zeigen und mit der echten Taste nach unten gehen.

Eine von 5 Components in Data Display

3 Variants

Variants

In Text und Menüs

Tasten im Satz und als Hinweis in einem Befehlsmenü.

Übersicht

Alle Kürzel in drei Gruppen, als Definitionsliste.

Größen und Stile

Standard, Kontur und Geist in drei Größen, dazu Folgen.

Details

Was Tasten mitbringt, aus Registry und Quellcode gelesen.

Kategorie
Data Display
Bereich
Elements
Variants
3
Version
1.0.0 · seit 1.0.0
Datei
components/ui/kbd.tsx
Bringt mit
utilskiticon
npm-Pakete
Keine
Sprachen
Englisch und Deutsch, 15 eigene Texte
Studio
Im Code anpassen

Props

Die Schnittstelle ShortcutProps aus components/ui/kbd.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.

  • keysPflicht
    string[]

    Keys pressed together, e.g. ['mod', 'k']. Special names: mod, shift, alt, enter, esc, backspace, tab, space, up, down, left, right.

  • modeoptional
    'together' | 'sequence'

    together: keys at the same time. sequence: one after the other ("G then D").

  • sizeoptional
    KbdSize

    smmdlg

  • variantoptional
    KbdVariant

    defaultoutlineghost

Eingebaute Texte

Diese Texte schreibt Tasten selbst. Die Sprache folgt dem KitProvider, über die Prop labels ersetzt du einzelne.

Alle 15 Texte anzeigen
thenEnglisch thenDeutsch dann
commandEnglisch CommandDeutsch Befehl
controlEnglisch ControlDeutsch Steuerung
shiftEnglisch ShiftDeutsch Umschalt
optionEnglisch OptionDeutsch Wahl
altEnglisch AltDeutsch Alt
enterEnglisch EnterDeutsch Eingabe
escapeEnglisch EscapeDeutsch Escape
backspaceEnglisch BackspaceDeutsch Rücktaste
tabEnglisch TabDeutsch Tab
spaceEnglisch SpaceDeutsch Leertaste
upEnglisch Arrow upDeutsch Pfeil hoch
downEnglisch Arrow downDeutsch Pfeil runter
leftEnglisch Arrow leftDeutsch Pfeil links
rightEnglisch Arrow rightDeutsch Pfeil rechts

Barrierefreiheit

Aus dem Quellcode der Component abgeleitet.

  • Tasten reagiert selbst auf die Tasten Escape, Enter, Pfeil hoch, Pfeil runter, Pfeil links, Pfeil rechts, Tab und Rücktaste.
  • Ergänzt Text für Screenreader, wo Icons und Grafiken allein stehen.
  • Der Server rendert den Startzustand vollständig, JavaScript ergänzt nur die Interaktion.

Bewegung

  • Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig. 1 Stellen im Code schalten Bewegung dann ab.
  • Ohne Animationen, sie steht sofort ruhig da.

Quellcode

Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.

Datei anzeigen, 230 Zeilen
components/ui/kbd.tsx
'use client';

import { useEffect, useState, useSyncExternalStore } from 'react';
import { cn, useTexts } from '@/lib/kit';
import { Icon, type IconName } from '@/components/ui/icon';

export type KbdSize = 'sm' | 'md' | 'lg';
export type KbdVariant = 'default' | 'outline' | 'ghost';

const SIZE: Record<KbdSize, string> = {
  sm: 'h-5 min-w-5 rounded-[4px] px-1 text-[11px]',
  md: 'h-6 min-w-6 rounded-[5px] px-1.5 text-[12px]',
  lg: 'h-8 min-w-8 rounded-[7px] px-2.5 text-[14px]',
};

const VARIANT: Record<KbdVariant, string> = {
  default: 'border border-border-strong bg-surface text-muted-foreground shadow-[inset_0_-1.5px_0_var(--border-strong),var(--highlight)]',
  outline: 'border border-border-strong bg-background text-foreground',
  ghost: 'bg-[color-mix(in_oklab,var(--foreground)_7%,transparent)] text-muted-foreground',
};

/* ------------------------------------------------------------------ */
/* Held keys                                                           */
/* ------------------------------------------------------------------ */

/*
 * Every key cap on the page reads one shared set of held keys, so a page
 * with fifty caps still has one pair of listeners. A cap whose key is held
 * on the real keyboard presses itself down. Names follow the Shortcut keys:
 * mod is Command on Apple devices and Control elsewhere.
 */
const held = new Set<string>();
const listeners = new Set<() => void>();
const FACE_NAME: Record<string, string> = {
  '⌘': 'cmd', '⌃': 'ctrl', '⇧': 'shift', '⌥': 'alt', '↵': 'enter', '⏎': 'enter', '⌫': 'backspace', '⇥': 'tab', '↑': 'up', '↓': 'down', '←': 'left', '→': 'right',
  esc: 'esc', escape: 'esc', return: 'enter', command: 'cmd', control: 'ctrl', strg: 'ctrl', option: 'alt', opt: 'alt',
};
const normal = (name: string) => {
  const n = name.trim().toLowerCase();
  return FACE_NAME[n] ?? FACE_NAME[name.trim()] ?? n;
};
function namesOf(e: KeyboardEvent, mac: boolean): string[] {
  switch (e.key) {
    case 'Meta': return mac ? ['cmd', 'mod'] : ['cmd'];
    case 'Control': return mac ? ['ctrl'] : ['ctrl', 'mod'];
    case 'Shift': return ['shift'];
    case 'Alt': return ['alt'];
    case 'Enter': return ['enter'];
    case 'Escape': return ['esc'];
    case 'Backspace': return ['backspace'];
    case 'Tab': return ['tab'];
    case ' ': return ['space'];
    case 'ArrowUp': return ['up'];
    case 'ArrowDown': return ['down'];
    case 'ArrowLeft': return ['left'];
    case 'ArrowRight': return ['right'];
    default: return e.key.length === 1 ? [e.key.toLowerCase()] : [e.key.toLowerCase()];
  }
}
function subscribe(cb: () => void) {
  listeners.add(cb);
  if (listeners.size === 1) {
    const mac = /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent);
    const emit = () => listeners.forEach((l) => l());
    const down = (e: KeyboardEvent) => {
      if (e.repeat) return;
      namesOf(e, mac).forEach((n) => held.add(n));
      emit();
    };
    const up = (e: KeyboardEvent) => {
      // With Command held, browsers on Apple devices skip the keyup of the letter: releasing Command lets go of everything
      if (e.key === 'Meta') held.clear();
      else namesOf(e, mac).forEach((n) => held.delete(n));
      emit();
    };
    const clear = () => {
      held.clear();
      emit();
    };
    document.addEventListener('keydown', down, true);
    document.addEventListener('keyup', up, true);
    window.addEventListener('blur', clear);
    stop = () => {
      document.removeEventListener('keydown', down, true);
      document.removeEventListener('keyup', up, true);
      window.removeEventListener('blur', clear);
    };
  }
  return () => {
    listeners.delete(cb);
    if (!listeners.size) stop?.();
  };
}
let stop: (() => void) | undefined;
const idle = () => () => {};

/** true while the named key is held on the real keyboard. Always false on the server. */
function useHeld(name: string | undefined) {
  return useSyncExternalStore(
    name ? subscribe : idle,
    () => (name ? held.has(name) : false),
    () => false,
  );
}

export interface KbdProps {
  size?: KbdSize;
  variant?: KbdVariant;
  /** The key this cap stands for, so it presses down with the real key, e.g. "k", "shift", "mod". Read from a text child by default. */
  keyName?: string;
  className?: string;
  children: React.ReactNode;
}

/** One key, drawn as a quiet key cap. It presses itself down while the same key is held on the keyboard. */
export function Kbd({ size = 'md', variant = 'default', keyName, className, children }: KbdProps) {
  const name = keyName ? normal(keyName) : typeof children === 'string' && children.length <= 6 ? normal(children) : undefined;
  const down = useHeld(name);
  return (
    <kbd
      data-pressed={down || undefined}
      className={cn(
        'inline-flex shrink-0 items-center justify-center whitespace-nowrap font-sans font-medium leading-none tracking-normal transition-[translate,scale,box-shadow,background-color,border-color,color] duration-150 ease-[var(--ease-kit)] motion-reduce:transition-none',
        SIZE[size],
        VARIANT[variant],
        'data-pressed:translate-y-px data-pressed:scale-[0.96] data-pressed:border-[color-mix(in_oklab,var(--primary)_55%,var(--border-strong))] data-pressed:bg-[color-mix(in_oklab,var(--primary)_14%,var(--surface))] data-pressed:text-foreground data-pressed:shadow-[inset_0_1px_1px_color-mix(in_oklab,var(--foreground)_10%,transparent)]',
        className,
      )}
    >
      {children}
    </kbd>
  );
}

const TEXTS = {
  en: { then: 'then', command: 'Command', control: 'Control', shift: 'Shift', option: 'Option', alt: 'Alt', enter: 'Enter', escape: 'Escape', backspace: 'Backspace', tab: 'Tab', space: 'Space', up: 'Arrow up', down: 'Arrow down', left: 'Arrow left', right: 'Arrow right' },
  de: { then: 'dann', command: 'Befehl', control: 'Steuerung', shift: 'Umschalt', option: 'Wahl', alt: 'Alt', enter: 'Eingabe', escape: 'Escape', backspace: 'Rücktaste', tab: 'Tab', space: 'Leertaste', up: 'Pfeil hoch', down: 'Pfeil runter', left: 'Pfeil links', right: 'Pfeil rechts' },
};
type KbdTexts = (typeof TEXTS)['en'];

const ICON_SIZE: Record<KbdSize, number> = { sm: 11, md: 12, lg: 15 };

/** Is this an Apple device? Always false on the server, checked after mount. */
function useIsMac() {
  const [mac, setMac] = useState(false);
  useEffect(() => {
    setMac(/Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent));
  }, []);
  return mac;
}

/** Symbol and spoken name of a special key. `mod` is Command on Apple devices and Control elsewhere. */
function keyFace(key: string, mac: boolean, t: KbdTexts): { face: string; name?: string; icon?: IconName } {
  switch (key.toLowerCase()) {
    case 'mod':
      return mac ? { face: '⌘', name: t.command } : { face: 'Ctrl', name: t.control };
    case 'cmd':
    case 'command':
      return { face: '⌘', name: t.command };
    case 'ctrl':
    case 'control':
      return { face: mac ? '⌃' : 'Ctrl', name: t.control };
    case 'shift':
      return { face: '⇧', name: t.shift };
    case 'alt':
    case 'option':
      return mac ? { face: '⌥', name: t.option } : { face: 'Alt', name: t.alt };
    case 'enter':
    case 'return':
      return { face: '↵', name: t.enter, icon: 'corner-down-left' };
    case 'esc':
    case 'escape':
      return { face: 'Esc', name: t.escape };
    case 'backspace':
      return { face: '⌫', name: t.backspace };
    case 'tab':
      return { face: '⇥', name: t.tab };
    case 'space':
      return { face: t.space };
    case 'up':
      return { face: '↑', name: t.up, icon: 'arrow-up' };
    case 'down':
      return { face: '↓', name: t.down, icon: 'arrow-down' };
    case 'left':
      return { face: '←', name: t.left, icon: 'arrow-left' };
    case 'right':
      return { face: '→', name: t.right, icon: 'arrow-right' };
    default:
      return { face: key.length === 1 ? key.toUpperCase() : key };
  }
}

export interface ShortcutProps {
  /** Keys pressed together, e.g. ['mod', 'k']. Special names: mod, shift, alt, enter, esc, backspace, tab, space, up, down, left, right. */
  keys: string[];
  /** together: keys at the same time. sequence: one after the other ("G then D"). */
  mode?: 'together' | 'sequence';
  size?: KbdSize;
  variant?: KbdVariant;
  labels?: Partial<KbdTexts>;
  className?: string;
}

/** A keyboard shortcut as key caps. Shows Command on Apple devices and Ctrl elsewhere for `mod`. */
export function Shortcut({ keys, mode = 'together', size = 'md', variant = 'default', labels, className }: ShortcutProps) {
  const t = useTexts(TEXTS, labels);
  const mac = useIsMac();
  return (
    <kbd className={cn('inline-flex items-center gap-1 align-middle font-sans', className)}>
      {keys.map((k, i) => {
        const { face, name, icon } = keyFace(k, mac, t);
        return (
          <span key={`${k}-${i}`} className="inline-flex items-center gap-1">
            {i > 0 && mode === 'sequence' && <span className="px-0.5 text-[12px] text-subtle-foreground">{t.then}</span>}
            <Kbd size={size} variant={variant} keyName={k}>
              {name ? (
                <>
                  {icon ? <Icon name={icon} size={ICON_SIZE[size]} strokeWidth={2} /> : <span aria-hidden>{face}</span>}
                  <span className="sr-only">{name}</span>
                </>
              ) : (
                face
              )}
            </Kbd>
          </span>
        );
      })}
    </kbd>
  );
}

Häufige Fragen

Wie installiere ich Tasten?

Mit aiw add @ai-website/kbd. Der Befehl legt die Datei components/ui/kbd.tsx in dein Projekt, der Code gehört danach dir. Mit dabei sind icon. Weitere npm-Pakete braucht sie nicht.

Welche Varianten hat Tasten?

3 Varianten, In Text und Menüs, Übersicht und Größen und Stile.

Kann ich die Texte von Tasten übersetzen?

Ja. Tasten bringt 15 eigene Texte auf Deutsch und Englisch mit, etwa „dann“, „Befehl“ und „Steuerung“. Die Sprache folgt dem KitProvider, einzelne Texte ersetzt du über die Prop labels.

Kann ein KI-Assistent Tasten einbauen?

Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Tasten mit aiw_components und installieren sie mit aiw_add @ai-website/kbd. Nenn im Prompt die Variante, etwa variant="outline", dann schreibt der Assistent deine Inhalte direkt in die Props.

Oft gesucht

Direkt zu

In die Zwischenablage kopiert