feat: add Kbd component - #886
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds the semantic Sequence Diagram(s)sequenceDiagram
actor User
participant Button
participant Tooltip
participant Kbd
User->>Button: Activate outline button
Button->>Tooltip: Trigger tooltip
Tooltip->>Kbd: Render Open search shortcut
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
commit: |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/www/src/content/docs/components/kbd/demo.ts`:
- Around line 5-8: In apps/www/src/content/docs/components/kbd/demo.ts, add
descriptive aria-label values to every symbol-only Kbd example: label Command at
lines 5-8, 13-19, 24-34, 42-46, 61-68, and 73-86; additionally label Shift and
Enter in the 13-19 example and Shift in the 24-34 example. Use labels such as
“Command,” “Shift,” and “Enter” while preserving the displayed symbols.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b976dc67-dfd8-49f0-97d5-98284057b0fe
📒 Files selected for processing (9)
apps/www/src/content/docs/components/kbd/demo.tsapps/www/src/content/docs/components/kbd/index.mdxapps/www/src/content/docs/components/kbd/props.tspackages/raystack/components/kbd/__tests__/data-slots.test.tsxpackages/raystack/components/kbd/__tests__/kbd.test.tsxpackages/raystack/components/kbd/index.tsxpackages/raystack/components/kbd/kbd.module.csspackages/raystack/components/kbd/kbd.tsxpackages/raystack/index.tsx
| /** | ||
| * Renders a `<kbd>` rather than a `<div>`: per the HTML spec a `kbd` nested | ||
| * inside a `kbd` represents an individual key within a larger input, which is | ||
| * exactly a shortcut sequence. | ||
| */ |
There was a problem hiding this comment.
Please remove comments
| color: var(--rs-color-foreground-base-tertiary); | ||
| font-family: var(--rs-font-body); | ||
| font-size: var(--rs-font-size-mini); | ||
| line-height: var(--rs-line-height-mini); | ||
| letter-spacing: var(--rs-letter-spacing-mini); |
There was a problem hiding this comment.
font color and styles should be applied only on .kbd and not the group so that there is a difference of text between two sets of .kbd
There was a problem hiding this comment.
Create 2 variants
- solid (default)
background: var(--rs-color-background-neutral-primary);
color: color: var(--rs-color-foreground-base-secondary);
- ghost
background: transparent;
color: color: var(--rs-color-foreground-base-tertiary);
| letter-spacing: var(--rs-letter-spacing-mini); | ||
| } | ||
|
|
||
| .kbd { |
There was a problem hiding this comment.
Should have pointer-events: none and user-select: none
| justify-content: center; | ||
| box-sizing: border-box; | ||
| height: var(--rs-space-6); | ||
| /* Square minimum so a narrow "K" reads the same width as a wide "⌘". */ |
| box-sizing: border-box; | ||
| height: var(--rs-space-6); | ||
| /* Square minimum so a narrow "K" reads the same width as a wide "⌘". */ | ||
| min-width: var(--rs-space-6); |
There was a problem hiding this comment.
Also add width: fit-content or else it will stretch in certain cases
|
|
||
| ## Accessibility | ||
|
|
||
| - `Kbd` is presentational and renders the semantic `<kbd>` element, which screen readers announce as keyboard input. |
There was a problem hiding this comment.
which screen readers announce as keyboard input. is misleading here, remove it
There was a problem hiding this comment.
Let's add a playground too since we are introducing variants now
| ### Inline with text | ||
|
|
||
| Keys sit on the text baseline, so they can be dropped into a sentence. | ||
|
|
||
| <Demo data={withTextDemo} /> |
There was a problem hiding this comment.
Even tho the example says Inline with text, the actual example is rendering in side a Flex, so it's misleading
There was a problem hiding this comment.
There is a sub-component in Command named Command.Shortcut
That should now be an alias of Kbd component with the default as ghost. All props should be forwarded
Addresses the review on #886. Component: - Add `solid` (default) and `ghost` variants. `variant` on `Kbd.Group` propagates to its keys via context, with a per-key override. - Add `width: fit-content` so a key no longer stretches in a column-flex or grid parent, and `pointer-events: none` alongside `user-select: none`. - Move typography onto `.kbd` only; `.kbd-group` takes `font: inherit` so separator text picks up the surrounding type instead of the key styling (and still escapes normalize's monospace default for `kbd`). - Drop the explanatory comments. Command: - `Command.Shortcut` is now an alias of `Kbd.Group` + `Kbd` defaulting to `ghost`, forwarding all props. Whitespace splitting and the `command-shortcut` / `command-shortcut-key` slots are preserved, and element children pass through without a second key wrapper. - Remove the now-unused shortcut typography. Accessibility: - `ghost` uses `foreground-base-secondary`. `tertiary` measured 3.33-4.46:1 across the surfaces Kbd is documented on, below AA's 4.5 for 11px text and worst on a hovered row; `secondary` clears it everywhere at >=5.19:1. - Correct the docs claim about screen readers: `kbd` maps to no ARIA role and no accessible object per HTML-AAM. - Label symbol-only keys in the examples. Docs: - Add a playground and a Variants section, reword the description and the Group summary, and make the "Inline with text" example actually inline. - Add an Input example, using a single key since the trailing slot is sized for an icon and clips a multi-key group. - Document `variant` on `Command.Shortcut`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/www/src/content/docs/components/kbd/props.ts`:
- Around line 21-25: Align the variant documentation across all three sites: in
apps/www/src/content/docs/components/kbd/props.ts lines 21-25, describe the
group variant as the inherited default for child keys, with explicit child Kbd
variants taking precedence; in
apps/www/src/content/docs/components/command/props.ts lines 141-145, document
that child variants override the shortcut variant; and in
apps/www/src/content/docs/components/command/index.mdx line 100, state that
ghost is the default variant rather than the only supported variant.
- Around line 7-11: The documentation prop interfaces in
apps/www/src/content/docs/components/kbd/props.ts (lines 7-11 and 21-25) and
apps/www/src/content/docs/components/command/props.ts (lines 135-146) omit
native element attributes and refs. Extend or reuse the published Kbd and
Command component types so the generated tables include supported properties
such as id, title, and aria-* attributes while preserving the existing
component-specific props.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 5bc9bed4-0236-4b95-82d6-68f442080194
📒 Files selected for processing (12)
apps/www/src/content/docs/components/command/index.mdxapps/www/src/content/docs/components/command/props.tsapps/www/src/content/docs/components/kbd/demo.tsapps/www/src/content/docs/components/kbd/index.mdxapps/www/src/content/docs/components/kbd/props.tspackages/raystack/components/command/__tests__/command.test.tsxpackages/raystack/components/command/command-misc.tsxpackages/raystack/components/command/command.module.csspackages/raystack/components/kbd/__tests__/kbd.test.tsxpackages/raystack/components/kbd/index.tsxpackages/raystack/components/kbd/kbd.module.csspackages/raystack/components/kbd/kbd.tsx
💤 Files with no reviewable changes (1)
- packages/raystack/components/command/command.module.css
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/raystack/components/kbd/index.tsx
- packages/raystack/components/kbd/kbd.tsx
- apps/www/src/content/docs/components/kbd/index.mdx
| /** | ||
| * Visual style variant. Inherited from a parent `Kbd.Group` when set there. | ||
| * @defaultValue "solid" | ||
| */ | ||
| variant?: 'solid' | 'ghost'; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
fd -i 'kbd|command' . | head -80
printf '%s\n' '--- Kbd props ---'
cat -n apps/www/src/content/docs/components/kbd/props.ts
printf '%s\n' '--- Command props ---'
sed -n '110,165p' apps/www/src/content/docs/components/command/props.ts
printf '%s\n' '--- relevant symbols ---'
rg -n --glob '!node_modules' 'KbdProps|KbdGroupProps|CommandShortcutProps|forwardRef|<kbd|...props|variant' apps/www/src packages src 2>/dev/null | head -240Repository: raystack/apsara
Length of output: 26103
🏁 Script executed:
#!/bin/bash
set -eu
for f in \
apps/www/src/components/kbd/kbd.tsx \
packages/raystack/components/kbd/kbd.tsx \
packages/raystack/components/command/command.tsx \
packages/raystack/components/command/command-misc.tsx \
apps/www/src/content/docs/components/kbd/index.mdx \
apps/www/src/content/docs/components/command/index.mdx
do
if [ -f "$f" ]; then
printf '\n--- %s (%s lines) ---\n' "$f" "$(wc -l < "$f")"
ast-grep outline "$f" || true
fi
done
printf '\n--- implementation excerpts ---\n'
cat -n apps/www/src/components/kbd/kbd.tsx
printf '\n--- package Kbd ---\n'
cat -n packages/raystack/components/kbd/kbd.tsx
printf '\n--- command references ---\n'
rg -n -C 5 'Shortcut|shortcut|Kbd' packages/raystack/components/command apps/www/src/content/docs/components/command apps/www/src/content/docs/components/kbd apps/www/src/components/kbdRepository: raystack/apsara
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- package metadata ---'
rg -n -C 3 '"react"|"typescript"|`@types/react`|auto-type-table' \
package.json pnpm-lock.yaml yarn.lock package-lock.json \
packages apps 2>/dev/null | head -220
printf '%s\n' '--- documentation prop inheritance patterns ---'
rg -n -C 2 'ComponentProps|HTMLAttributes|ButtonHTMLAttributes|extends .*Props|native attributes|forward.*ref' \
apps/www/src/content/docs/components -g 'props.ts' | head -260
printf '%s\n' '--- relevant tests ---'
sed -n '315,365p' packages/raystack/components/command/__tests__/command.test.tsx
sed -n '1,150p' packages/raystack/components/kbd/__tests__/kbd.test.tsx
printf '%s\n' '--- exports ---'
rg -n -C 3 'components/kbd|components/command|export.*Kbd|export.*Command' packages/raystack -g '*.ts' -g '*.tsx' -g 'package.json' | head -180Repository: raystack/apsara
Length of output: 42826
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
docs = {
"KbdProps": Path("apps/www/src/content/docs/components/kbd/props.ts"),
"KbdGroupProps": Path("apps/www/src/content/docs/components/kbd/props.ts"),
"CommandShortcutProps": Path("apps/www/src/content/docs/components/command/props.ts"),
}
impl = Path("packages/raystack/components/kbd/kbd.tsx").read_text()
command = Path("packages/raystack/components/command/command-misc.tsx").read_text()
print("--- implementation contracts ---")
for name in ("KbdProps", "KbdGroupProps"):
m = re.search(rf"(?:export )?type {name}\s*=\s*([^;]+);", impl)
print(f"{name}: {m.group(1).strip() if m else 'NOT FOUND'}")
m = re.search(r"export type CommandShortcutProps\s*=\s*([^;]+);", command)
print(f"CommandShortcutProps: {m.group(1).strip() if m else 'NOT FOUND'}")
print("--- forwarding sites ---")
for name, text, pattern in [
("KbdRoot", impl, r"<kbd\b[\s\S]*?\{\.\.\.props\}"),
("KbdGroup", impl, r"<kbd\b[\s\S]*?\{\.\.\.props\}"),
("CommandShortcut", command, r"<Kbd\.Group[\s\S]*?\{\.\.\.props\}"),
]:
print(f"{name}: {'yes' if re.search(pattern, text) else 'no'}")
print("--- documented native/ref surface ---")
for name, path in docs.items():
text = path.read_text()
block = re.search(
rf"export interface {name}\s*\{{([\s\S]*?)\n\}}", text
)
body = block.group(1) if block else ""
native = bool(re.search(r"ComponentProps|HTMLAttributes|KbdProps|KbdGroupProps", body))
ref = bool(re.search(r"\bref\b|Ref", body))
print(f"{name}: native-base={'yes' if native else 'no'}, ref-member={'yes' if ref else 'no'}")
print("--- forwarding tests ---")
tests = Path("packages/raystack/components/kbd/__tests__/kbd.test.tsx").read_text()
command_tests = Path("packages/raystack/components/command/__tests__/command.test.tsx").read_text()
for label, text, needles in [
("Kbd", tests, ("forwards arbitrary props", "forwards ref")),
("Command.Shortcut", command_tests, ("forwards props", "forwards ref")),
]:
print(f"{label}: " + ", ".join(f"{n}={'yes' if n in text else 'no'}" for n in needles))
PY
printf '%s\n' '--- type-table and generated-doc configuration ---'
rg -n -C 4 'auto-type-table|fumadocs-typescript|fumadocs-docgen|TypeTable|props\.ts' \
apps/www package.json pnpm-lock.yaml 2>/dev/null | head -260Repository: raystack/apsara
Length of output: 17302
Align the documentation prop tables with the published component types.
The package already types and forwards native <kbd> attributes and refs. The three documentation interfaces omit this surface, so their generated tables do not match the documented id, title, and aria-* support. Extend or reuse the package types in the documentation tables.
📍 Affects 2 files
apps/www/src/content/docs/components/kbd/props.ts#L7-L11(this comment)apps/www/src/content/docs/components/kbd/props.ts#L21-L25apps/www/src/content/docs/components/command/props.ts#L135-L146
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/www/src/content/docs/components/kbd/props.ts` around lines 7 - 11, The
documentation prop interfaces in
apps/www/src/content/docs/components/kbd/props.ts (lines 7-11 and 21-25) and
apps/www/src/content/docs/components/command/props.ts (lines 135-146) omit
native element attributes and refs. Extend or reuse the published Kbd and
Command component types so the generated tables include supported properties
such as id, title, and aria-* attributes while preserving the existing
component-specific props.
| /** | ||
| * Visual style variant applied to every key in the group. | ||
| * @defaultValue "solid" | ||
| */ | ||
| variant?: 'solid' | 'ghost'; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use one variant contract in all API documentation.
The runtime treats group and shortcut variants as inherited defaults. Explicit child Kbd variants override them. The documentation currently describes the variant as unconditional and describes Command.Shortcut as always ghost.
apps/www/src/content/docs/components/kbd/props.ts#L21-L25: Document the group variant as the default inherited by child keys.apps/www/src/content/docs/components/command/props.ts#L141-L145: Document that child variants override the shortcut variant.apps/www/src/content/docs/components/command/index.mdx#L100-L100: State that ghost is the default, not the only supported variant.
📍 Affects 3 files
apps/www/src/content/docs/components/kbd/props.ts#L21-L25(this comment)apps/www/src/content/docs/components/command/props.ts#L141-L145apps/www/src/content/docs/components/command/index.mdx#L100-L100
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/www/src/content/docs/components/kbd/props.ts` around lines 21 - 25,
Align the variant documentation across all three sites: in
apps/www/src/content/docs/components/kbd/props.ts lines 21-25, describe the
group variant as the inherited default for child keys, with explicit child Kbd
variants taking precedence; in
apps/www/src/content/docs/components/command/props.ts lines 141-145, document
that child variants override the shortcut variant; and in
apps/www/src/content/docs/components/command/index.mdx line 100, state that
ghost is the default variant rather than the only supported variant.
Description
Adds
Kbd, a component for displaying keyboard keys and shortcut sequences.Apsara has no shared way to render a keyboard key today, so it gets hand-rolled with inline styles wherever it appears.
Command.Shortcutrenders unstyled<kbd>elements,Menuhas no shortcut support, and tooltips have no way to show an action's shortcut.The API follows shadcn's Kbd, converted to Apsara conventions: dot-notation sub-components and
--rs-*tokens. The visual treatment reuses the existing key styling fromCommand.Shortcutand the docs-siteKbd, so no new tokens were added.One note for review:
Kbd.Grouprenders a<kbd>rather than a<div>, since the HTML spec defines a nestedkbdas an individual key within a larger input.Type of Change
How Has This Been Tested?
classNamemerging, ref forwarding, composition, and thedata-slotcontract. Full suite: 2589 passed, 1 skipped.pnpm build:apsaraandpnpm --filter www buildboth pass; the docs page generates and appears in the sidebar.tsc --noEmitandbiome checkclean on all new files.Checklist:
Screenshots (if appropriate):
N/A
Related Issues
N/A