Kbd
Keyboard key cap; flat by default, elevation raises the key.
import { Kbd, KbdGroup } from "@/components/ui/kbd";
export default function Demo() { return ( <div className="flex flex-wrap items-center gap-6"> <Kbd>K</Kbd> <KbdGroup> <Kbd>⌘</Kbd> <Kbd>K</Kbd> </KbdGroup> <Kbd>Enter</Kbd> <span className="text-sm"> Press{" "} <KbdGroup> <Kbd>⌘</Kbd> <Kbd>J</Kbd> </KbdGroup>{" "} to ask the agent </span> </div> );}<script setup lang="ts">import { Kbd, KbdGroup } from "@/components/ui/kbd";</script>
<template> <div class="flex flex-wrap items-center gap-6"> <Kbd>K</Kbd> <KbdGroup> <Kbd>⌘</Kbd> <Kbd>K</Kbd> </KbdGroup> <Kbd>Enter</Kbd> <span class="text-sm"> Press <KbdGroup> <Kbd>⌘</Kbd> <Kbd>J</Kbd> </KbdGroup> to ask the agent </span> </div></template><script lang="ts"> import * as Kbd from "$lib/components/ui/kbd";</script>
<div class="flex flex-wrap items-center gap-6"> <Kbd.Root>K</Kbd.Root> <Kbd.Group> <Kbd.Root>⌘</Kbd.Root> <Kbd.Root>K</Kbd.Root> </Kbd.Group> <Kbd.Root>Enter</Kbd.Root> <span class="text-sm"> Press <Kbd.Group> <Kbd.Root>⌘</Kbd.Root> <Kbd.Root>J</Kbd.Root> </Kbd.Group> to ask the agent </span></div>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/kbdpnpm dlx shadcn@latest add @edmi-ui/kbdyarn dlx shadcn@latest add @edmi-ui/kbdbunx shadcn@latest add @edmi-ui/kbdPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/kbdpnpm dlx shadcn-vue@latest add @edmi-ui/kbdyarn dlx shadcn-vue@latest add @edmi-ui/kbdbunx shadcn-vue@latest add @edmi-ui/kbdPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/kbd.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/kbd.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/kbd.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/kbd.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { Kbd, KbdGroup } from "@/components/ui/kbd";
<div className="flex flex-wrap items-center gap-6"> <Kbd>K</Kbd> <KbdGroup> <Kbd>⌘</Kbd> <Kbd>K</Kbd> </KbdGroup> <Kbd>Enter</Kbd> <span className="text-sm"> Press{" "} <KbdGroup> <Kbd>⌘</Kbd> <Kbd>J</Kbd> </KbdGroup>{" "} to ask the agent </span></div><script setup lang="ts">import { Kbd, KbdGroup } from "@/components/ui/kbd";</script>
<template> <div class="flex flex-wrap items-center gap-6"> <Kbd>K</Kbd> <KbdGroup> <Kbd>⌘</Kbd> <Kbd>K</Kbd> </KbdGroup> <Kbd>Enter</Kbd> <span class="text-sm"> Press <KbdGroup> <Kbd>⌘</Kbd> <Kbd>J</Kbd> </KbdGroup> to ask the agent </span> </div></template><script lang="ts"> import * as Kbd from "$lib/components/ui/kbd";</script>
<div class="flex flex-wrap items-center gap-6"> <Kbd.Root>K</Kbd.Root> <Kbd.Group> <Kbd.Root>⌘</Kbd.Root> <Kbd.Root>K</Kbd.Root> </Kbd.Group> <Kbd.Root>Enter</Kbd.Root> <span class="text-sm"> Press <Kbd.Group> <Kbd.Root>⌘</Kbd.Root> <Kbd.Root>J</Kbd.Root> </Kbd.Group> to ask the agent </span></div>Component source
Section titled “Component source”"use client";
import { cva, type VariantProps } from "class-variance-authority";import { cn } from "cn";
import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation";
const kbdVariants = cva( "pointer-events-none inline-flex h-[22px] w-fit min-w-[22px] items-center justify-center gap-1 rounded-[5px] border border-input bg-muted px-1.5 font-mono text-[11.5px] font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:border-[color-mix(in_srgb,var(--background)_20%,var(--primary))] in-data-[slot=tooltip-content]:bg-[color-mix(in_srgb,var(--background)_10%,var(--primary))] in-data-[slot=tooltip-content]:text-background [&_svg:not([class*='size-'])]:size-3", { variants: { // ✦ depth (v4); inside a tooltip the key stays flat elevation: { flat: "", sunken: "", raised: "border-transparent [background-image:var(--r1-s-face)] [background-origin:border-box] shadow-btn-raised-neutral in-data-[slot=tooltip-content]:bg-none in-data-[slot=tooltip-content]:shadow-none", floating: "border-transparent [background-image:var(--fl-s-face)] [background-origin:border-box] shadow-btn-float-neutral in-data-[slot=tooltip-content]:bg-none in-data-[slot=tooltip-content]:shadow-none", }, }, defaultVariants: { elevation: "flat" }, },);
function Kbd({ className, elevation, ...props}: React.ComponentProps<"kbd"> & Omit<VariantProps<typeof kbdVariants>, "elevation"> & { /** ✦ depth: flat 0, raised +1, floating +2. */ elevation?: Elevation; }) { const level = useElevation(elevation, "handle"); return ( <kbd data-slot="kbd" className={cn(kbdVariants({ elevation: level }), className)} {...props} /> );}
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) { return ( <kbd data-slot="kbd-group" className={cn("inline-flex items-center gap-1", className)} {...props} /> );}
export { Kbd, KbdGroup, kbdVariants };<script setup lang="ts">import type { HTMLAttributes } from 'vue'import { cn } from '@/registry/edmi/lib/utils'import { type Elevation, useElevation } from '@/registry/edmi/ui/elevation'import { kbdVariants } from '.'
const props = withDefaults(defineProps<{ class?: HTMLAttributes['class'] /** ✦ depth: flat 0, raised +1, floating +2 */ elevation?: Elevation}>(), { elevation: undefined })
const level = useElevation(() => props.elevation, 'handle')</script>
<template> <kbd data-slot="kbd" :class="cn(kbdVariants({ elevation: level }), props.class)" > <slot /> </kbd></template><script setup lang="ts">import type { HTMLAttributes } from 'vue'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{ class?: HTMLAttributes['class']}>()</script>
<template> <kbd data-slot="kbd-group" :class="cn('inline-flex items-center gap-1', props.class)" > <slot /> </kbd></template>import type { VariantProps } from "class-variance-authority";import { cva } from "class-variance-authority";
export { default as Kbd } from "./Kbd.vue";export { default as KbdGroup } from "./KbdGroup.vue";
export const kbdVariants = cva( "pointer-events-none inline-flex h-[22px] w-fit min-w-[22px] items-center justify-center gap-1 rounded-[5px] border border-input bg-muted px-1.5 font-mono text-[11.5px] font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:border-[color-mix(in_srgb,var(--background)_20%,var(--primary))] in-data-[slot=tooltip-content]:bg-[color-mix(in_srgb,var(--background)_10%,var(--primary))] in-data-[slot=tooltip-content]:text-background [&_svg:not([class*='size-'])]:size-3", { variants: { // ✦ depth (v4); inside a tooltip the key stays flat elevation: { flat: "", sunken: "", raised: "border-transparent [background-image:var(--r1-s-face)] [background-origin:border-box] shadow-btn-raised-neutral in-data-[slot=tooltip-content]:bg-none in-data-[slot=tooltip-content]:shadow-none", floating: "border-transparent [background-image:var(--fl-s-face)] [background-origin:border-box] shadow-btn-float-neutral in-data-[slot=tooltip-content]:bg-none in-data-[slot=tooltip-content]:shadow-none", }, }, defaultVariants: { elevation: "flat" }, },);
export type KbdVariants = VariantProps<typeof kbdVariants>;<script lang="ts" module> import { tv, type VariantProps } from "tailwind-variants";
export const kbdVariants = tv({ base: "pointer-events-none inline-flex h-[22px] w-fit min-w-[22px] items-center justify-center gap-1 rounded-[5px] border border-input bg-muted px-1.5 font-mono text-[11.5px] font-medium text-muted-foreground select-none in-data-[slot=tooltip-content]:border-[color-mix(in_srgb,var(--background)_20%,var(--primary))] in-data-[slot=tooltip-content]:bg-[color-mix(in_srgb,var(--background)_10%,var(--primary))] in-data-[slot=tooltip-content]:text-background [&_svg:not([class*='size-'])]:size-3", variants: { // ✦ depth (v4); inside a tooltip the key stays flat elevation: { flat: "", sunken: "", raised: "border-transparent [background-image:var(--r1-s-face)] [background-origin:border-box] shadow-btn-raised-neutral in-data-[slot=tooltip-content]:bg-none in-data-[slot=tooltip-content]:shadow-none", floating: "border-transparent [background-image:var(--fl-s-face)] [background-origin:border-box] shadow-btn-float-neutral in-data-[slot=tooltip-content]:bg-none in-data-[slot=tooltip-content]:shadow-none", }, }, defaultVariants: { elevation: "flat" }, });
export type KbdVariants = VariantProps<typeof kbdVariants>;</script>
<script lang="ts"> import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js"; import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { ref = $bindable(null), class: className, children, elevation = "auto", ...restProps }: WithElementRef<HTMLAttributes<HTMLElement>> & { /** ✦ depth: flat 0, raised +1, floating +2. */ elevation?: Elevation; } = $props();
const level = useElevation(() => elevation, "handle");</script>
<kbd bind:this={ref} data-slot="kbd" class={cn(kbdVariants({ elevation: level.current }), className)} {...restProps}> {@render children?.()}</kbd><script lang="ts"> import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLElement>> = $props();</script>
<kbd bind:this={ref} data-slot="kbd-group" class={cn("inline-flex items-center gap-1", className)} {...restProps}> {@render children?.()}</kbd>import Root from "./kbd.svelte";import Group from "./kbd-group.svelte";
export { Group, Group as KbdGroup, Root, // Root as Kbd,};Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Set it on Kbd (not KbdGroup). raised (+1) bevels the key cap, floating (+2) adds a soft drop. Inside a tooltip the key stays flat.
Flat (0)
Raised (+1)
Floating (+2)
Flat (0)
Raised (+1)
Floating (+2)
Flat (0)
Raised (+1)
Floating (+2)
import { Kbd, KbdGroup } from "@/components/ui/kbd";
const levels = [ { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" },] as const;
export default function Demo() { return ( <div className="flex flex-col gap-5"> {levels.map(({ value, label }) => ( <div key={value} className="flex flex-col gap-2"> <p className="text-xs font-medium text-muted-foreground">{label}</p> <div className="flex flex-wrap items-center gap-6"> <Kbd elevation={value}>K</Kbd> <KbdGroup> <Kbd elevation={value}>⌘</Kbd> <Kbd elevation={value}>K</Kbd> </KbdGroup> <Kbd elevation={value}>Enter</Kbd> </div> </div> ))} </div> );}<script setup lang="ts">import { Kbd, KbdGroup } from "@/components/ui/kbd";
const levels = [{ value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }];</script>
<template> <div class="flex flex-col gap-5"> <div v-for="level in levels" :key="level.value" class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{{ level.label }}</p> <div class="flex flex-wrap items-center gap-6"> <Kbd :elevation="level.value">K</Kbd> <KbdGroup> <Kbd :elevation="level.value">⌘</Kbd> <Kbd :elevation="level.value">K</Kbd> </KbdGroup> <Kbd :elevation="level.value">Enter</Kbd> </div> </div> </div></template><script lang="ts"> import * as Kbd from "$lib/components/ui/kbd";
const levels = [{ value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }] as const;</script>
<div class="flex flex-col gap-5"> {#each levels as level (level.value)} <div class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{level.label}</p> <div class="flex flex-wrap items-center gap-6"> <Kbd.Root elevation={level.value}>K</Kbd.Root> <Kbd.Group> <Kbd.Root elevation={level.value}>⌘</Kbd.Root> <Kbd.Root elevation={level.value}>K</Kbd.Root> </Kbd.Group> <Kbd.Root elevation={level.value}>Enter</Kbd.Root> </div> </div> {/each}</div>| Prop | Type | Default |
|---|---|---|
| className | string |
none |
elevation |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
Parts: Kbd, KbdGroup.
✦ Edmi additions
Section titled “✦ Edmi additions”- Flat key cap (
--muted, 1px border, mono);elevationadds the bevel. Adapts insidedata-slot=tooltip-content.