Components
Kbd
An inline keyboard-key badge. Renders a native <kbd> for a single key, or nests one <kbd> per key inside an outer one to render a shortcut like Ctrl + Shift + R. Zero JavaScript — it's a label, not a control.
Installation
1. Install via the shadcn CLI
npx shadcn@latest add http://localhost/r/kbd.json2. Use it
import { Kbd, KbdGroup } from "@/components/ui/kbd"
<Kbd>Esc</Kbd>
<KbdGroup keys={["Ctrl", "Shift", "R"]} />
<KbdGroup>
<Kbd>⌘</Kbd> + <Kbd>K</Kbd>
</KbdGroup>Or copy the source manually
/** @jsxImportSource hono/jsx */
import type { Child, PropsWithChildren } from "hono/jsx"
import { cn, type ClassValue } from "@/registry/lib/cn"
// Kbd — shadcn-htmx, htmx v4 + Tailwind v4.
//
// Native element (source of truth):
// repos/mdn/files/en-us/web/html/reference/elements/kbd/index.md
// - <kbd> denotes textual user input from a keyboard / voice / other
// text-entry device. Implicit ARIA role: "no corresponding role"
// (it's phrasing content, announced as plain text). No interaction,
// so this component ships ZERO JavaScript.
// - MDN's "Representing keystrokes within an input" pattern nests a
// <kbd> per key inside an outer <kbd> that represents the whole
// shortcut. We follow that: <KbdGroup> is the outer <kbd>, each
// <Kbd> is an inner key. A lone <Kbd> (no group) is still a valid
// single-key <kbd>.
//
// Visual styling translated from shadcn/ui's Kbd (kept on-brand with our
// theme tokens, not copied verbatim):
// repos/shadcn-ui/apps/v4/registry/new-york-v4/ui/kbd.tsx
//
// We use ONLY existing theme tokens (bg-muted / text-muted-foreground /
// border). select-none + pointer-events-none keep the key glyphs from being
// selected or clicked — they are a label, not a control.
const kbdBase =
"pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 " +
"rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground " +
"[&_svg:not([class*='size-'])]:size-3"
// The outer <kbd> that wraps a sequence of keys. MDN allows nesting <kbd>
// inside <kbd>; the group carries no background of its own so the inner
// keys read as discrete caps with separators (the "+" text) between them.
const kbdGroupBase = "inline-flex w-fit items-center gap-1 align-middle"
export function kbdClasses(opts?: { class?: ClassValue }): string {
return cn(kbdBase, opts?.class)
}
export function kbdGroupClasses(opts?: { class?: ClassValue }): string {
return cn(kbdGroupBase, opts?.class)
}
type KbdProps = PropsWithChildren<{
class?: ClassValue
id?: string
// Accessible name override — useful for symbol-only keys (e.g. content
// "⌘" with ariaLabel="Command"). See aria-label on MDN.
ariaLabel?: string
title?: string
// htmx attrs / data-* / aria-* flow through via {...rest}.
}>
// A single key cap. Renders one <kbd>. Use inside <KbdGroup> for shortcuts.
export function Kbd(props: KbdProps) {
const { children, class: className, id, ariaLabel, title, ...rest } = props
return (
<kbd
id={id}
data-slot="kbd"
class={kbdClasses({ class: className })}
aria-label={ariaLabel}
title={title}
{...rest}
>
{children}
</kbd>
)
}
type KbdGroupProps = PropsWithChildren<{
class?: ClassValue
id?: string
// Convenience: render a shortcut from a list of key labels, joined by a
// visible separator. Omit `keys` and pass children to compose manually.
keys?: Child[]
// Separator rendered between keys (default "+"). Set "" for none.
separator?: Child
// Accessible name for the whole shortcut, e.g. "Control Shift R". The
// outer <kbd> is the labelled unit; per-key caps inherit no extra role.
ariaLabel?: string
}>
// The outer <kbd> wrapping a key sequence (MDN nested-kbd pattern). When
// `keys` is supplied it renders one <Kbd> per entry with a separator text
// node between them; otherwise it renders `children` verbatim so callers
// can mix keys, "+" text, and icons by hand.
export function KbdGroup(props: KbdGroupProps) {
const {
children,
class: className,
id,
keys,
separator = "+",
ariaLabel,
...rest
} = props
let body: Child
if (keys && keys.length > 0) {
const parts: Child[] = []
keys.forEach((k, i) => {
if (i > 0 && separator !== "" && separator != null) {
parts.push(
<span aria-hidden="true" class="text-muted-foreground/70">
{separator}
</span>,
)
}
parts.push(<Kbd>{k}</Kbd>)
})
body = parts
} else {
body = children
}
return (
<kbd
id={id}
data-slot="kbd-group"
class={kbdGroupClasses({ class: className })}
aria-label={ariaLabel}
{...rest}
>
{body}
</kbd>
)
}
1. Save the file
Copy kbd.html into templates/components/.
2. Use it
{% from "components/kbd.html" import kbd, kbd_group %}
{{ kbd("Esc") }}
{{ kbd_group(["Ctrl", "Shift", "R"]) }}View source
{# Kbd macros — shadcn-htmx, htmx v4 + Tailwind v4.
Mirrors registry/ui/kbd.tsx. Zero JavaScript — <kbd> is phrasing
content with no interaction.
Native element:
repos/mdn/files/en-us/web/html/reference/elements/kbd/index.md
(nested <kbd> per key inside an outer <kbd> = MDN keystroke pattern)
Usage:
{% from "components/kbd.html" import kbd, kbd_group %}
{{ kbd("Esc") }}
{{ kbd_group(["Ctrl", "Shift", "R"]) }}
{% call kbd_group() %}<kbd ...>⌘</kbd> + <kbd ...>K</kbd>{% endcall %} #}
{%- set kbd_base -%}
pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3
{%- endset -%}
{%- set kbd_group_base -%}
inline-flex w-fit items-center gap-1 align-middle
{%- endset -%}
{# A single key cap → one <kbd>. #}
{% macro kbd(
text,
id=none,
aria_label=none,
title=none,
extra_class="",
**attrs
) %}
<kbd
{%- if id %} id="{{ id }}"{% endif %}
data-slot="kbd"
class="{{ kbd_base }} {{ extra_class }}"
{%- if aria_label %} aria-label="{{ aria_label }}"{% endif %}
{%- if title %} title="{{ title }}"{% endif %}
{%- for k, v in attrs.items() %} {{ k|replace('_', '-') }}="{{ v }}"{% endfor -%}
>{{ text }}</kbd>
{%- endmacro %}
{# The outer <kbd> wrapping a shortcut. Pass `keys` (a list) to render a
key cap per entry joined by `separator`, or use {% call %} with a body. #}
{% macro kbd_group(
keys=none,
separator="+",
id=none,
aria_label=none,
extra_class="",
**attrs
) %}
<kbd
{%- if id %} id="{{ id }}"{% endif %}
data-slot="kbd-group"
class="{{ kbd_group_base }} {{ extra_class }}"
{%- if aria_label %} aria-label="{{ aria_label }}"{% endif %}
{%- for k, v in attrs.items() %} {{ k|replace('_', '-') }}="{{ v }}"{% endfor -%}
>
{%- if keys -%}
{%- for key in keys -%}
{%- if not loop.first and separator -%}<span aria-hidden="true" class="text-muted-foreground/70">{{ separator }}</span>{%- endif -%}
<kbd data-slot="kbd" class="{{ kbd_base }}">{{ key }}</kbd>
{%- endfor -%}
{%- else -%}
{{ caller() }}
{%- endif -%}
</kbd>
{%- endmacro %}
1. Save the file
Add kbd.tmpl alongside your templates.
2. Use it
{{template "kbd" (dict "Text" "Esc")}}
{{template "kbd-group" (dict "Keys" (list "Ctrl" "Shift" "R"))}}View source
{{/*
Kbd templates — shadcn-htmx, htmx v4 + Tailwind v4.
Mirrors registry/ui/kbd.tsx. Zero JavaScript — <kbd> is phrasing content.
Native element:
repos/mdn/files/en-us/web/html/reference/elements/kbd/index.md
(nested <kbd> per key inside an outer <kbd> = MDN keystroke pattern)
Usage:
type KbdArgs struct {
Text string
ID, AriaLabel string
Title string
Attrs map[string]string
}
type KbdGroupArgs struct {
Keys []string // one cap per entry
Separator string // default "+"
ID, AriaLabel string
Body template.HTML // used when Keys is empty
Attrs map[string]string
}
{{template "kbd" (dict "Text" "Esc")}}
{{template "kbd-group" (dict "Keys" (list "Ctrl" "Shift" "R"))}}
*/}}
{{define "kbd"}}
{{- $base := "pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3" -}}
<kbd
{{- if .ID}} id="{{.ID}}"{{end}}
data-slot="kbd"
class="{{$base}}"
{{- if .AriaLabel}} aria-label="{{.AriaLabel}}"{{end}}
{{- if .Title}} title="{{.Title}}"{{end}}
{{- range $k, $v := .Attrs}} {{$k}}="{{$v}}"{{end -}}
>{{.Text}}</kbd>
{{end}}
{{define "kbd-group"}}
{{- $base := "pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3" -}}
{{- $groupBase := "inline-flex w-fit items-center gap-1 align-middle" -}}
{{- $sep := or .Separator "+" -}}
<kbd
{{- if .ID}} id="{{.ID}}"{{end}}
data-slot="kbd-group"
class="{{$groupBase}}"
{{- if .AriaLabel}} aria-label="{{.AriaLabel}}"{{end}}
{{- range $k, $v := .Attrs}} {{$k}}="{{$v}}"{{end -}}
>
{{- if .Keys -}}
{{- range $i, $key := .Keys -}}
{{- if and (gt $i 0) $sep -}}<span aria-hidden="true" class="text-muted-foreground/70">{{$sep}}</span>{{- end -}}
<kbd data-slot="kbd" class="{{$base}}">{{$key}}</kbd>
{{- end -}}
{{- else -}}
{{- htmlSafe .Body -}}
{{- end -}}
</kbd>
{{end}}
1. Save the file
Drop kbd.ex into lib/my_app_web/components/.
2. Use it
<.kbd>Esc</.kbd>
<.kbd_group keys={["Ctrl", "Shift", "R"]} />View source
defmodule ShadcnHtmx.Components.Kbd do
@moduledoc """
Kbd — shadcn-htmx, htmx v4 + Tailwind v4 for Phoenix.
Mirrors registry/ui/kbd.tsx. Zero JavaScript — `<kbd>` is phrasing
content with no interaction.
Native element:
repos/mdn/files/en-us/web/html/reference/elements/kbd/index.md
(nested `<kbd>` per key inside an outer `<kbd>` = MDN keystroke pattern)
`kbd/1` renders one key cap. `kbd_group/1` renders the outer `<kbd>`
wrapping a shortcut — pass `keys` for an auto-joined sequence, or use the
default slot to compose the body by hand.
"""
use Phoenix.Component
@base "pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 " <>
"rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground " <>
"[&_svg:not([class*='size-'])]:size-3"
@group_base "inline-flex w-fit items-center gap-1 align-middle"
attr :id, :string, default: nil
attr :aria_label, :string, default: nil
attr :title, :string, default: nil
attr :class, :string, default: nil
attr :rest, :global
slot :inner_block, required: true
def kbd(assigns) do
assigns = assign(assigns, :base_class, @base)
~H"""
<kbd
id={@id}
data-slot="kbd"
class={[@base_class, @class]}
aria-label={@aria_label}
title={@title}
{@rest}
>
{render_slot(@inner_block)}
</kbd>
"""
end
attr :keys, :list, default: nil
attr :separator, :string, default: "+"
attr :id, :string, default: nil
attr :aria_label, :string, default: nil
attr :class, :string, default: nil
attr :rest, :global
slot :inner_block
def kbd_group(assigns) do
assigns =
assigns
|> assign(:base_class, @base)
|> assign(:group_class, @group_base)
~H"""
<kbd
id={@id}
data-slot="kbd-group"
class={[@group_class, @class]}
aria-label={@aria_label}
{@rest}
>
<%= if @keys && @keys != [] do %>
<%= for {key, i} <- Enum.with_index(@keys) do %>
<span :if={i > 0 && @separator != ""} aria-hidden="true" class="text-muted-foreground/70">{@separator}</span>
<kbd data-slot="kbd" class={@base_class}>{key}</kbd>
<% end %>
<% else %>
{render_slot(@inner_block)}
<% end %>
</kbd>
"""
end
end
1. Save the file
Paste the markup; relies only on theme tokens.
2. Use it
<kbd data-slot="kbd"
class="pointer-events-none inline-flex h-5 …">Esc</kbd>View source
<!--
shadcn-htmx — raw <kbd> snippets. Mirrors registry/ui/kbd.tsx.
Zero JavaScript — <kbd> is phrasing content with no interaction.
Native element:
repos/mdn/files/en-us/web/html/reference/elements/kbd/index.md
(nested <kbd> per key inside an outer <kbd> = MDN keystroke pattern)
KEY CAP base (data-slot="kbd"):
pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none
items-center justify-center gap-1 rounded-sm border bg-muted px-1
font-sans text-xs font-medium text-muted-foreground
[&_svg:not([class*='size-'])]:size-3
GROUP base (data-slot="kbd-group", the outer wrapping <kbd>):
inline-flex w-fit items-center gap-1 align-middle
Relies only on theme tokens; no script.
-->
<!-- Single key -->
<kbd data-slot="kbd"
class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">
Esc
</kbd>
<!-- Shortcut: outer <kbd> groups inner key caps, "+" between them -->
<kbd data-slot="kbd-group" class="inline-flex w-fit items-center gap-1 align-middle">
<kbd data-slot="kbd"
class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">Ctrl</kbd>
<span aria-hidden="true" class="text-muted-foreground/70">+</span>
<kbd data-slot="kbd"
class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">Shift</kbd>
<span aria-hidden="true" class="text-muted-foreground/70">+</span>
<kbd data-slot="kbd"
class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">R</kbd>
</kbd>
<!-- Symbol key with an accessible name (aria-label) for screen readers -->
<kbd data-slot="kbd" aria-label="Command"
class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">
⌘
</kbd>
Examples
Single key
A lone <Kbd> renders one native <kbd>. Symbol-only keys take an ariaLabel so screen readers announce a word, not a glyph.
<kbd> is phrasing content with no implicit ARIA role — it's read as plain text. For a symbol cap such as ⌘ pass ariaLabel="Command" so assistive tech doesn't announce the raw glyph.
<Kbd>Esc</Kbd>
<Kbd>Enter</Kbd>
<Kbd>Tab</Kbd>
<Kbd ariaLabel="Command">⌘</Kbd>
<Kbd ariaLabel="Up arrow">↑</Kbd>{{ kbd("Esc") }}
{{ kbd("Enter") }}
{{ kbd("Tab") }}
{{ kbd("⌘", aria_label="Command") }}
{{ kbd("↑", aria_label="Up arrow") }}{{template "kbd" (dict "Text" "Esc")}}
{{template "kbd" (dict "Text" "Enter")}}
{{template "kbd" (dict "Text" "Tab")}}
{{template "kbd" (dict "Text" "⌘" "AriaLabel" "Command")}}
{{template "kbd" (dict "Text" "↑" "AriaLabel" "Up arrow")}}<.kbd>Esc</.kbd>
<.kbd>Enter</.kbd>
<.kbd>Tab</.kbd>
<.kbd aria_label="Command">⌘</.kbd>
<.kbd aria_label="Up arrow">↑</.kbd><div class="flex flex-wrap items-center justify-center gap-3">
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">Esc</kbd>
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">Enter</kbd>
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">Tab</kbd>
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3" aria-label="Command">⌘</kbd>
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3" aria-label="Up arrow">↑</kbd>
</div>Further reading
Shortcut — nested keys
A shortcut is one outer <kbd> wrapping a <kbd> per key, with a "+" separator between them (MDN's keystroke pattern). Pass keys to build it, or compose by hand.
Per MDN, a multi-keystroke input nests each key in its own <kbd> inside an outer <kbd> that represents the whole input. The + separators are aria-hidden so the reading isn't cluttered with "plus".
<KbdGroup keys={["Ctrl", "Shift", "R"]} />
<KbdGroup keys={["⌘", "K"]} ariaLabel="Command K" />
<KbdGroup keys={["Alt", "F4"]} />{{ kbd_group(["Ctrl", "Shift", "R"]) }}
{{ kbd_group(["⌘", "K"], aria_label="Command K") }}
{{ kbd_group(["Alt", "F4"]) }}{{template "kbd-group" (dict "Keys" (list "Ctrl" "Shift" "R"))}}
{{template "kbd-group" (dict "Keys" (list "⌘" "K") "AriaLabel" "Command K")}}
{{template "kbd-group" (dict "Keys" (list "Alt" "F4"))}}<.kbd_group keys={["Ctrl", "Shift", "R"]} />
<.kbd_group keys={["⌘", "K"]} aria_label="Command K" />
<.kbd_group keys={["Alt", "F4"]} /><div class="flex flex-wrap items-center justify-center gap-4">
<kbd data-slot="kbd-group" class="inline-flex w-fit items-center gap-1 align-middle">
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">Ctrl</kbd>
<span aria-hidden="true" class="text-muted-foreground/70">+</span>
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">Shift</kbd>
<span aria-hidden="true" class="text-muted-foreground/70">+</span>
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">R</kbd>
</kbd>
<kbd data-slot="kbd-group" class="inline-flex w-fit items-center gap-1 align-middle" aria-label="Command K">
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">⌘</kbd>
<span aria-hidden="true" class="text-muted-foreground/70">+</span>
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">K</kbd>
</kbd>
<kbd data-slot="kbd-group" class="inline-flex w-fit items-center gap-1 align-middle">
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">Alt</kbd>
<span aria-hidden="true" class="text-muted-foreground/70">+</span>
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">F4</kbd>
</kbd>
</div>Further reading
Inline in prose
Kbd is phrasing content, so it sits inside running text, menu rows, and tooltips without breaking the line box.
Because <kbd> is inline phrasing content it flows with the surrounding sentence. The cap is select-none and pointer-events-none so copying the paragraph skips the glyphs and clicks fall through to whatever wraps it.
Press / to focus search, or CtrlK to open the command menu. Save with ⌘S.
<p>
Press <Kbd>/</Kbd> to focus search, or{" "}
<KbdGroup keys={["Ctrl", "K"]} /> to open the command menu.
Save with <KbdGroup keys={["⌘", "S"]} ariaLabel="Command S" />.
</p><p>
Press {{ kbd("/") }} to focus search, or
{{ kbd_group(["Ctrl", "K"]) }} to open the command menu.
Save with {{ kbd_group(["⌘", "S"], aria_label="Command S") }}.
</p><p>
Press {{template "kbd" (dict "Text" "/")}} to focus search, or
{{template "kbd-group" (dict "Keys" (list "Ctrl" "K"))}} to open the command menu.
Save with {{template "kbd-group" (dict "Keys" (list "⌘" "S") "AriaLabel" "Command S")}}.
</p><p>
Press <.kbd>/</.kbd> to focus search, or
<.kbd_group keys={["Ctrl", "K"]} /> to open the command menu.
Save with <.kbd_group keys={["⌘", "S"]} aria_label="Command S" />.
</p><div class="max-w-md text-sm text-muted-foreground">
<p>
Press
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">/</kbd>
to focus search, or
<kbd data-slot="kbd-group" class="inline-flex w-fit items-center gap-1 align-middle">
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">Ctrl</kbd>
<span aria-hidden="true" class="text-muted-foreground/70">+</span>
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">K</kbd>
</kbd>
to open the command menu. Save with
<kbd data-slot="kbd-group" class="inline-flex w-fit items-center gap-1 align-middle" aria-label="Command S">
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">⌘</kbd>
<span aria-hidden="true" class="text-muted-foreground/70">+</span>
<kbd data-slot="kbd" class="pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3">S</kbd>
</kbd>
.
</p>
</div>Further reading
API Reference
<Kbd>
| Prop | Type | Default | Description |
|---|---|---|---|
keys | Child[] | — | KbdGroup only. Render one key cap (a nested <kbd>) per entry, joined by separator. Omit and pass children to compose a shortcut by hand. Follows MDN's nested-<kbd> keystroke pattern.MDNRepresenting keystrokes within an input |
separator | Child | "+" | KbdGroup only. Node rendered between keys (aria-hidden so it is not announced). Set "" for no separator. |
ariaLabel | string | — | Accessible name. Use on symbol-only caps (e.g. "⌘" with ariaLabel="Command") so AT announces a word, not the raw glyph; or on a KbdGroup to name the whole shortcut.MDNaria-label |
title | string | — | Kbd only. Native browser tooltip shown on hover.MDNtitle attribute |
class | string | — | Extra Tailwind classes appended to the root element. |
hx-* | any | — | Any htmx attribute. Forwarded onto the underlying element.htmxAttribute reference |