c2-kbd renders a keyboard key. It wraps a native <kbd>, so a screen reader announces it as keyboard input rather than as decoration, and it is styled entirely through CSS variables — cap height, radius, the optional shadow that gives it depth, and the monospace stack.
Use one element for a whole shortcut (Ctrl + K) when it reads as a single chord, or one element per key when you want the separators to sit between real keycaps. Both are correct; the second lets you style each cap.
Import
import '@c2n/components/kbd'
Usage
Ctrl + KCtrl + KCtrl + Shift + PCtrl + Shift + P⌘ + ⌥ + I⌘ + ⌥ + I Press // to search, or ?? for every shortcut.
Importimport '@c2n/components/kbd'
Markup<style>
.hint {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: color-mix(in srgb, var(--c2-theme--color-on-surface, #18181b) 74%, var(--c2-theme--color-surface, #ffffff));
}
.sep {
color: var(--c2-theme--color-outline-strong, #a1a1aa);
font-size: 12px;
}
.raised {
--c2-kbd--background-color: var(--c2-theme--color-surface-container, #f4f4f5);
--c2-kbd--color: color-mix(in srgb, var(--c2-theme--color-on-surface, #18181b) 74%, var(--c2-theme--color-surface, #ffffff));
--c2-kbd--border: 1px solid var(--c2-theme--color-outline, #d4d4d8);
--c2-kbd--box-shadow: 0 1px 0 var(--c2-theme--color-outline-strong, #a1a1aa);
}
.dark {
--c2-kbd--background-color: color-mix(in srgb, var(--c2-theme--color-on-surface, #18181b) 93%, var(--c2-theme--color-surface, #ffffff));
--c2-kbd--color: var(--c2-theme--color-outline-variant, #e4e4e7);
--c2-kbd--border: 1px solid color-mix(in srgb, var(--c2-theme--color-on-surface, #18181b) 82%, var(--c2-theme--color-surface, #ffffff));
--c2-kbd--box-shadow: 0 1px 0 var(--c2-theme--color-inverse-surface, #18181b);
}
.round {
--c2-kbd--border-radius: 999px;
--c2-kbd--min-width: 28px;
--c2-kbd--height: 28px;
}
</style>
<div data-label="Single keys">
<c2-kbd>⌘</c2-kbd>
<c2-kbd>K</c2-kbd>
<c2-kbd>⇧</c2-kbd>
<c2-kbd>Esc</c2-kbd>
<c2-kbd>Enter</c2-kbd>
</div>
<div data-label="A whole shortcut">
<c2-kbd>Ctrl + K</c2-kbd>
<c2-kbd>Ctrl + Shift + P</c2-kbd>
<c2-kbd>⌘ + ⌥ + I</c2-kbd>
</div>
<div data-label="Key by key">
<span class="hint">
<c2-kbd>Ctrl</c2-kbd>
<span class="sep">+</span>
<c2-kbd>Shift</c2-kbd>
<span class="sep">+</span>
<c2-kbd>P</c2-kbd>
</span>
</div>
<div data-label="In a sentence">
<span class="hint">Press <c2-kbd>/</c2-kbd> to search, or <c2-kbd>?</c2-kbd> for every shortcut.</span>
</div>
<div data-label="Themed">
<c2-kbd class="raised">Tab</c2-kbd>
<c2-kbd class="dark">⌘ + K</c2-kbd>
<c2-kbd class="round">A</c2-kbd>
</div>
The element is presentational: it never takes focus and carries no interactive role. When the same shortcut is already announced by the control it belongs to — a menu item that reads “Search, Command K” — set aria-hidden="true" on the host so it is not read twice.
--c2-kbd--min-width keeps single letters square while longer labels such as Enter grow past it, so a row of mixed keys stays even. The default is flat; set --c2-kbd--box-shadow (for example 0 1px 0 #a1a1aa) for a raised keycap, which is the whole difference between the two.