---
name: kbd
title: Kbd
url: https://ui.frontic.com/components/ui/kbd.md
tier: ui
install: npx @frontic/ui add kbd
files:
  - components/ui/kbd/Kbd.vue
  - components/ui/kbd/KbdGroup.vue
  - components/ui/kbd/index.ts
dependencies:
  - reka-ui
---

# Kbd

Shows a keyboard key or shortcut, such as the key that focuses the search.

## Install

```sh
npx @frontic/ui add kbd
```

npm packages: `reka-ui`

## Examples

```vue
<script setup lang="ts">
import { SearchIcon } from '@lucide/vue'

import { onBeforeUnmount, onMounted, useTemplateRef } from 'vue'
import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group'
import { Kbd } from '@/components/ui/kbd'

const input = useTemplateRef<{ $el: HTMLInputElement }>('input')

function onKeydown(event: KeyboardEvent) {
  if (event.key !== '/' || event.metaKey || event.ctrlKey || event.altKey) return
  const target = event.target as HTMLElement | null
  if (target?.closest('input, textarea, select, [contenteditable=""], [contenteditable="true"]')) return
  event.preventDefault()
  input.value?.$el.focus()
}
onMounted(() => window.addEventListener('keydown', onKeydown))
onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
</script>

<template>
  <InputGroup class="max-w-sm">
    <InputGroupInput ref="input" placeholder="Search products" aria-label="Search products" aria-keyshortcuts="/" />
    <InputGroupAddon>
      <SearchIcon data-icon />
    </InputGroupAddon>
    <InputGroupAddon align="inline-end">
      <Kbd>/</Kbd>
    </InputGroupAddon>
  </InputGroup>
</template>
```

The example also uses `input-group`, which `add kbd` leaves out: `npx @frontic/ui add input-group`.

## API reference

### Kbd

Shows a keyboard key or shortcut. Renders a `<kbd>`.

`data-slot="kbd"` · `Kbd.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'kbd'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

### KbdGroup

Groups several keys into one shortcut. Renders a `<kbd>`.

`data-slot="kbd-group"` · `KbdGroup.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'kbd'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

## Styling

| Part | Recipe |
| --- | --- |
| `group` | [`kbd-group`](https://ui.frontic.com/DESIGN.md#kbd-group) |
| `default` | [`kbd`](https://ui.frontic.com/DESIGN.md#kbd) |

## Source

`components/ui/kbd/Kbd.vue`

```vue
<script lang="ts">
/** Shows a keyboard key or shortcut. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'kbd' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="kbd" :class="['fui-kbd pointer-events-none select-none', props.class]">
    <slot />
  </Primitive>
</template>
```

`components/ui/kbd/KbdGroup.vue`

```vue
<script lang="ts">
/** Groups several keys into one shortcut. */
export default {}
</script>

<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import { Primitive } from 'reka-ui'
import type { HTMLAttributes } from 'vue'

const props = withDefaults(
  defineProps<
    PrimitiveProps & {
      class?: HTMLAttributes['class']
    }
  >(),
  { as: 'kbd' },
)
</script>

<template>
  <Primitive :as="props.as" :as-child="props.asChild" data-slot="kbd-group" :class="['fui-kbd-group', props.class]">
    <slot />
  </Primitive>
</template>
```

## API of the items the example uses

### Input Group

[Full docs](https://ui.frontic.com/components/ui/input-group.md) · `npx @frontic/ui add input-group`

#### InputGroup

Groups an input with addons such as icons, buttons or text. Renders a `<div>`.

`data-slot="input-group"` · `InputGroup.vue`

Styling: `input-group`, which extends [`control`](https://ui.frontic.com/DESIGN.md#control). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `size` | `InputGroupSize`: `'md' \| 'sm'` |  | The height of the group; the input inside it follows. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-size` | `'md' \| 'sm'` | Carries the `size` prop. |

#### InputGroupAddon

Places an icon, text or button beside the input of its group. Renders a `<div>`.

`data-slot="input-group-addon"` · `InputGroupAddon.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'div'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `align` | `InputGroupAddonAlign`: `'inline-start' \| 'inline-end' \| 'block-start' \| 'block-end'` | `'inline-start'` | Where the addon sits: before or after the input on its line, or above or below it. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-align` | `'inline-start' \| 'inline-end' \| 'block-start' \| 'block-end'` | Carries the `align` prop. |

#### InputGroupButton

Lets the user act on the input of its group, such as submit it.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | `'ghost'` | The button style: solid, outline, soft, ghost or link. |
| `color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | `'neutral'` | The button colour role, e.g. neutral or brand. |
| `size` | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | `'xs'` | The button's size inside the field; the `icon-*` sizes make a square, icon-only button. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | Carries the `color` prop. |
| `data-size` | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | Carries the `size` prop. |
| `data-variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | Carries the `variant` prop. |

#### InputGroupInput

Lets the user enter a single line of text inside the group.

`data-slot="input-group-control"` · `InputGroupInput.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

#### InputGroupText

Shows fixed text, such as a unit or prefix, inside the group. Renders a `<span>`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'span'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

#### InputGroupTextarea

Lets the user enter several lines of text inside the group.

`data-slot="input-group-control"` · `InputGroupTextarea.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
