Skip to content
Palette Default
Download ZIP

Default palette · 5.7 KB ZIP File receipt View as Markdown View code

Preview

Fit to the available width. The frame follows the height of its content; previews taller than the maximum auto-height scroll inside it.

Give this component to your coding agent Copy a prompt that fetches this exact version and palette through the PageSugar MCP server.
cmp_keyboard_shortcut_hint_01 · version 1.0.0 · Default palette
Using the PageSugar MCP server, fetch component cmp_keyboard_shortcut_hint_01 version 1.0.0 with variant "default", first inspect its requirements and license status and confirm this project uses Svelte 5 and Tailwind CSS 4. Retrieve every manifest file, including binary assets and any manifest-only response files, preserving relative paths. Then integrate the source and follow its usage notes. Run project checks, review the browser result and report anything unverified. Do not substitute another version or invent missing files.

Not connected yet? Set up the MCP server

Code

Palette
Default
Version
1.0.0
Digest
Full digest
sha256-75d0d7d9b5e4b2bd63bad86f8fc433420eed37a03160201ee6dc103875fc9a9e
KeyboardShortcut.svelte Svelte · 6.2 KB Raw
<script lang="ts" module>
	export type KeyboardShortcutSize = 'sm' | 'md';
	export type KeyboardShortcutPlatform = 'mac' | 'other';
</script>

<script lang="ts">
	interface Props {
		/** Keys in the order they are pressed, e.g. ['Mod', 'K']. 'Mod' becomes ⌘ or Ctrl. */
		keys: string[];
		/** Keys pressed one after another rather than together; the separator becomes "then". */
		sequence?: boolean;
		/** Spoken names by key, e.g. { '⌘': 'Command' }. Merged over the built-in names. */
		labels?: Record<string, string>;
		/** Keycap size: sm is 20 px for prose and dense rows, md is 24 px. */
		size?: KeyboardShortcutSize;
		/** Platform the consumer has chosen. Maps Mod and the modifier names; never detected. */
		platform?: KeyboardShortcutPlatform;
		/** The word shown and spoken between the keys of a sequence. */
		thenLabel?: string;
		/** The word spoken (not shown) between the keys of a combination. */
		plusLabel?: string;
		/** Extra classes on the outer kbd, for margins or alignment. */
		class?: string;
	}

	let {
		keys,
		sequence = false,
		labels,
		size = 'md',
		platform = 'other',
		thenLabel = 'then',
		plusLabel = 'plus',
		class: className
	}: Props = $props();

	/*
	 * Modifier names a consumer writes once, shown the way each platform prints them. Keys are
	 * matched without case. Only 'mac' swaps names for glyphs; elsewhere only Mod changes.
	 */
	const MAC: Record<string, string> = {
		mod: '⌘',
		cmd: '⌘',
		command: '⌘',
		ctrl: '⌃',
		control: '⌃',
		alt: '⌥',
		option: '⌥',
		opt: '⌥',
		shift: '⇧',
		enter: '↵',
		return: '↵',
		backspace: '⌫'
	};
	const OTHER: Record<string, string> = { mod: 'Ctrl' };

	/* What a screen reader should say for keys whose printed form it reads badly or not at all. */
	const SPOKEN: Record<string, string> = {
		'⌘': 'Command',
		'⌥': 'Option',
		'⇧': 'Shift',
		'⌃': 'Control',
		'⌫': 'Delete',
		'⌦': 'Forward Delete',
		'⎋': 'Escape',
		'⇥': 'Tab',
		'⇪': 'Caps Lock',
		'←': 'Left Arrow',
		'→': 'Right Arrow',
		'↑': 'Up Arrow',
		'↓': 'Down Arrow',
		'␣': 'Space',
		Ctrl: 'Control',
		Esc: 'Escape',
		Del: 'Delete',
		PgUp: 'Page Up',
		PgDn: 'Page Down'
	};

	/*
	 * Modifier glyphs are drawn smaller than capital letters in most text faces, so they are
	 * scaled an eighth larger to sit at the letters' optical size. A scale, not a font size, so
	 * the glyph's metrics never move its cap off the shared baseline. Arrows already match.
	 */
	const GLYPHS = new Set(['⌘', '⌥', '⇧', '⌃', '⌫', '⌦', '⎋', '⇥', '⇪', '↵', '⏎']);

	/*
	 * One height and a square minimum width per size, so a lone "K" is a key, not a sliver. The
	 * caption line box is the cap's full height: the cap centres its label and shares that
	 * label's baseline with the text around the hint.
	 */
	const SIZES: Record<KeyboardShortcutSize, { root: string; cap: string }> = {
		sm: { root: 'text-xs', cap: 'h-5 min-w-5 rounded-sm px-1 leading-5' },
		md: { root: 'text-[0.8125rem]', cap: 'h-6 min-w-6 rounded-md px-1.5 leading-6' }
	};

	/** A map lookup that ignores inherited names, so a key called "constructor" stays text. */
	const own = (map: Record<string, string> | undefined, key: string) =>
		map && Object.hasOwn(map, key) ? map[key] : undefined;

	const step = $derived(Object.hasOwn(SIZES, size) ? SIZES[size] : SIZES.md);
	const mac = $derived(platform === 'mac');

	const caps = $derived(
		(keys ?? [])
			.map((key) => String(key).trim())
			.filter(Boolean)
			.map((key) => {
				const text = own(mac ? MAC : OTHER, key.toLowerCase()) ?? key;
				const enter = text === '↵' || text === '⏎' ? (mac ? 'Return' : 'Enter') : undefined;
				const spoken = own(labels, key) ?? own(labels, text) ?? own(SPOKEN, text) ?? enter;
				return {
					text,
					spoken: spoken && spoken !== text ? spoken : undefined,
					glyph: GLYPHS.has(text)
				};
			})
	);
</script>

<!--
	The shortcut is notation, so it keeps Latin key order on a right-to-left page (dir="ltr").
	Each separator travels with the key after it: the group wraps between steps, never leaving
	a "+" or "then" stranded at the end of a line.
-->
{#if caps.length}
	<kbd
		dir="ltr"
		class={[
			'keyboard-shortcut-hint inline-flex max-w-full flex-wrap items-baseline gap-1 align-baseline font-sans font-medium tracking-normal normal-case',
			step.root,
			className
		]}
	>
		{#each caps as cap, index (index)}
			<span class="inline-flex items-baseline gap-1 whitespace-nowrap">
				{#if index > 0}
					{#if sequence}
						<span class="font-normal text-(--_muted)" aria-hidden="true">{thenLabel}</span>
					{:else if !mac}
						<span class="font-normal text-(--_muted)" aria-hidden="true">+</span>
					{/if}
					<!-- Spaces inside the spoken word: flex gaps are not word boundaries to a screen reader. -->
					<span class="sr-only">{` ${sequence ? thenLabel : plusLabel} `}</span>
				{/if}
				<kbd
					class={[
						'keyboard-shortcut-hint__key inline-flex shrink-0 items-center justify-center bg-(--_surface) font-[inherit] whitespace-nowrap text-(--_ink) tabular-nums shadow-(--_cap) forced-colors:outline-1 forced-colors:-outline-offset-1 forced-colors:outline-solid',
						step.cap
					]}
				>
					{#if cap.spoken}
						<span class={cap.glyph ? 'inline-block scale-[1.125]' : undefined} aria-hidden="true"
							>{cap.text}</span
						><span class="sr-only">{cap.spoken}</span>
					{:else}
						{cap.text}
					{/if}
				</kbd>
			</span>
		{/each}
	</kbd>
{/if}

<style>
	/*
	 * Public tokens: set --keyboard-shortcut-hint-* on the hint or any ancestor to retone it. A
	 * hint paints no accent, so it has no accent tokens: surface is the keycap fill, ink its
	 * label, muted the separators, and hairline the cap edge.
	 */
	.keyboard-shortcut-hint {
		--_surface: var(--keyboard-shortcut-hint-surface, #ffffff);
		--_ink: var(--keyboard-shortcut-hint-ink, #52525b);
		--_muted: var(--keyboard-shortcut-hint-muted, #71717a);
		--_hairline: var(--keyboard-shortcut-hint-hairline, rgb(0 0 0 / 0.12));

		/*
		 * Formula: a hairline all round plus one more along the bottom, inset so the cap keeps
		 * its box. Light from above, one pixel of lip; never a bevel.
		 */
		--_cap: inset 0 0 0 1px var(--_hairline), inset 0 -1px 0 var(--_hairline);
	}
</style>

Pass the keys in order and, if you know it, the platform. It renders a kbd group of keycaps and nothing else: it does not register, handle or check the shortcut, does not detect the operating system, and does not reorder modifiers into a platform's conventional order.

Suggested location
src/lib/components/keyboard-shortcut-hint-01
Required props
keys

Limitations

  • It does not know the visitor's platform. Mod shows Ctrl unless you pass platform="mac"; choose it from a setting or a server-side hint so the server and the browser render the same thing.
  • Keys render in the order you give them. On a Mac the conventional order is Control, Option, Shift, Command; write them in that order if you want it.
  • Only the listed modifier names map to glyphs on a Mac. Any other key, Esc or Tab included, is shown as written.
  • The hint is always laid out left to right (dir="ltr"), so Ctrl + K keeps Latin key order on a right-to-left page. Pass thenLabel and plusLabel in the page's language; the spoken order is the order of keys.
  • The built-in spoken names are English. Pass labels, thenLabel and plusLabel for another language.
  • Light pages by default. On a dark surface set the four tokens; the customisation guide has the values.

Example

Svelte
<script lang="ts">
	import KeyboardShortcut from '$lib/components/keyboard-shortcut-hint-01/KeyboardShortcut.svelte';

	// Decide the platform yourself, for example from a user setting or a server-side header.
	const platform: 'mac' | 'other' = 'other';
</script>

<p>Press <KeyboardShortcut keys={['Mod', 'K']} {platform} size="sm" /> to search.</p>

<KeyboardShortcut keys={['Shift', 'Mod', 'P']} platform="mac" />
<KeyboardShortcut keys={['G', 'I']} sequence />
<KeyboardShortcut keys={['⌥', 'F']} labels={{ '⌥': 'Option' }} />

The source imports its own files by relative path only. Examples import from $lib/...; SvelteKit 3 projects have no $lib alias by default, so import from your #lib subpath (with the .svelte extension) or a relative path instead.

Keyboard shortcut hint#

Shows a shortcut the page already supports, as keycaps. It never listens for the keys itself.

Write keys once, choose the platform yourself#

Write the platform-dependent modifier as Mod, and pass platform when you know it:

Svelte
<KeyboardShortcut keys={['Mod', 'K']} platform="mac" />
<!-- ⌘ K, read "Command plus K" -->
<KeyboardShortcut keys={['Mod', 'K']} />
<!-- Ctrl + K, read "Control plus K" -->

The component does not sniff the user agent, because the server cannot see it and a guess made in the browser would change the markup after hydration. Take the platform from a user setting, or read a client hint such as Sec-CH-UA-Platform on the server and pass it down.

Sequences#

sequence joins the keys with a word instead of a plus: G then I. Pass thenLabel (shown and spoken) and plusLabel (spoken only) on non-English pages.

Inside prose#

Use size="sm". The 20 px caps fit inside a 16 px paragraph's line box, so the lines keep their spacing, and the hint sits on the text baseline.

Spoken names#

Glyphs such as ⌘ ⌥ ⇧ ⌃ ⌫ and the arrows carry a hidden spoken name. labels adds or overrides names: labels={{ '⌘': 'Cmd' }}.

Props and content inputs#

On this page
NameTypeRequiredDefaultDescription
keysstring[]YesNoneKeys in the order they are pressed, such as ['Mod', 'K']. Mod becomes ⌘ or Ctrl from platform. An empty array renders nothing.
sequencebooleanNofalseKeys pressed one after another rather than together. The separator becomes the word in thenLabel, shown and spoken.
labelsRecord<string, string>NoNoneSpoken names by key, such as { '⌘': 'Command' }, read by screen readers in place of the printed key. Merged over the built-in names for symbol keys.
size'sm' | 'md'No'md'sm is 20 px caps with 12 px text, for prose and dense rows; md is 24 px caps with 13 px text.
platform'mac' | 'other'No'other'The platform you have chosen. mac shows ⌘ ⇧ ⌥ ⌃ ↵ ⌫ for Mod, Shift, Alt, Ctrl, Enter and Backspace and drops the plus; other shows Mod as Ctrl. Never detected.
thenLabelstringNo'then'The word shown and spoken between the keys of a sequence.
plusLabelstringNo'plus'The word spoken, not shown, between the keys of a combination.
classstringNoNoneExtra classes on the outer kbd, for margins or alignment.

Customization#

On this page

Retone the keycaps with four --keyboard-shortcut-hint-* variables. Edit the MAC and SPOKEN maps in the source to change which names become glyphs and what screen readers say for them.

  • Keycap colours: --keyboard-shortcut-hint-surface is the cap fill, --keyboard-shortcut-hint-ink its label, --keyboard-shortcut-hint-muted the + and then separators, and --keyboard-shortcut-hint-hairline the cap's edge and its one-pixel bottom lip.
  • Dark or tinted page: set surface to rgb(255 255 255 / 0.06), ink to #d4d4d8, muted to #a1a1aa and hairline to rgb(255 255 255 / 0.14) on the dark container. The lip is a hairline, not a shadow, so it still shows on dark.
  • Cream or warm page: keep surface #ffffff so the caps lift off the page, and set hairline to a warm alpha such as rgb(60 40 20 / 0.16).
  • Platform: pass platform="mac" when you know the visitor is on a Mac, from a user setting or a server-side hint. Write Mod once in keys and both platforms read correctly.
  • Another language: pass thenLabel and plusLabel, and labels for any symbol key whose spoken name should change.
  • More glyphs on a Mac: add entries to MAC in the source, for example tab: '⇥', and give each new glyph a spoken name in SPOKEN.
  • Shape: the caps are 4 px (sm) and 6 px (md) rectangles. Change rounded-sm and rounded-md in SIZES; keep them below the cap's half-height so they stay keys, not pills.

Public CSS variables

VariableToken
--keyboard-shortcut-hint-surfacesurface
--keyboard-shortcut-hint-inkink
--keyboard-shortcut-hint-mutedmuted
--keyboard-shortcut-hint-hairlinehairline

Accessibility#

On this page
  • Renders an outer kbd holding one kbd per key, the nesting the HTML spec gives for a key combination. kbd has no role, so nothing announces it as a key; the spoken text below is what carries the meaning.
  • Symbol keys print a glyph hidden from assistive technology and a visually hidden name beside it, so ⌘ is read as Command rather than skipped or read as "place of interest sign".
  • The + and then separators are hidden from assistive technology and a spoken plus or then is supplied, so Ctrl + K reads as Control plus K and a sequence reads G then I.
  • It registers no keyboard listener and is not focusable. The shortcut it describes is yours to implement, and the hint should only show shortcuts that actually work on the page.
  • Put the hint where the shortcut's action is named, such as inside the trigger button it opens or next to a menu item, so the shortcut is announced with the action it runs.
  • The default keycap label is zinc-600 on its white cap (7.7:1) and the separators zinc-500, which clear 4.5:1 on white (4.8:1) and zinc-50 (4.6:1). In forced-colours mode each cap draws a system-colour outline in place of its hairline.

Known limitations

  • Contrast is checked for the default colours on white and zinc-50 pages. On zinc-100 or darker greys the then separator falls to 4.4:1; set --keyboard-shortcut-hint-muted to #52525b there. A retone is the consumer's to check.

Release details#

On this page
Integration
  • Presentational
  • Works without client-side JavaScript
  • Server-side rendering supported
Dependencies
No additional runtime packages beyond Svelte and Tailwind CSS
License

MIT. Default license approval is pending; see the license status before adopting the source.

Version history
  • 1.0.0 (Published) Current release · 21 October 2026

Only the current release is available. Keep downloaded source and its receipt if you need to use it again later.