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
Variants
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
- Mit KI bauen
- Per MCP in Claude, Cursor, Copilot und Codex
Props
Die Schnittstelle ShortcutProps aus components/ui/kbd.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.
keysPflichtstring[]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").
sizeoptionalKbdSizesmmdlg
variantoptionalKbdVariantdefaultoutlineghost
Eingebaute Texte
Diese Texte schreibt Tasten selbst. Die Sprache folgt dem KitProvider, über die Prop labels ersetzt du einzelne.
Alle 15 Texte anzeigen
| Schlüssel | Englisch | Deutsch |
|---|---|---|
| then | Englisch then | Deutsch dann |
| command | Englisch Command | Deutsch Befehl |
| control | Englisch Control | Deutsch Steuerung |
| shift | Englisch Shift | Deutsch Umschalt |
| option | Englisch Option | Deutsch Wahl |
| alt | Englisch Alt | Deutsch Alt |
| enter | Englisch Enter | Deutsch Eingabe |
| escape | Englisch Escape | Deutsch Escape |
| backspace | Englisch Backspace | Deutsch Rücktaste |
| tab | Englisch Tab | Deutsch Tab |
| space | Englisch Space | Deutsch Leertaste |
| up | Englisch Arrow up | Deutsch Pfeil hoch |
| down | Englisch Arrow down | Deutsch Pfeil runter |
| left | Englisch Arrow left | Deutsch Pfeil links |
| right | Englisch Arrow right | Deutsch 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
'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.