Skip to content

Keyboard Key

A kbd element to display a keyboard key.

Demo

A

This requires the following composables to be installed:

This requires the following theme to be installed:

Component

Kbd.vue
vue
<script lang="ts">
import type { KbdKey } from '@/ui/composables/useKbd'
import type { VariantProps } from 'tailwind-variants'
import { useKbd } from '@/ui/composables/useKbd'
import theme from '@/ui/theme/kbd'
import { Primitive } from 'reka-ui'
import { tv } from 'tailwind-variants'

const kbd = tv(theme)

type KbdVariants = VariantProps<typeof kbd>

export interface KbdProps {
  as?: any
  value?: KbdKey | string
  variant?: KbdVariants['variant']
  size?: KbdVariants['size']
  class?: any
}

export interface KbdSlots {
  default: (props?: object) => any
}
</script>

<script setup lang="ts">
const props = withDefaults(defineProps<KbdProps>(), {
  as: 'kbd',
})
defineSlots<KbdSlots>()

const { getKbdKey } = useKbd()
</script>

<template>
  <Primitive :as="as" :class="kbd({ variant, size, class: props.class })">
    <slot>
      {{ getKbdKey(value) }}
    </slot>
  </Primitive>
</template>
Kbd.vue
vue
<script lang="ts">
import type { KbdKey } from '@/UI/Composables/useKbd'
import type { VariantProps } from 'tailwind-variants'
import { useKbd } from '@/UI/Composables/useKbd'
import theme from '@/UI/Theme/kbd'
import { Primitive } from 'reka-ui'
import { tv } from 'tailwind-variants'

const kbd = tv(theme)

type KbdVariants = VariantProps<typeof kbd>

export interface KbdProps {
  as?: any
  value?: KbdKey | string
  variant?: KbdVariants['variant']
  size?: KbdVariants['size']
  class?: any
}

export interface KbdSlots {
  default: (props?: object) => any
}
</script>

<script setup lang="ts">
const props = withDefaults(defineProps<KbdProps>(), {
  as: 'kbd',
})
defineSlots<KbdSlots>()

const { getKbdKey } = useKbd()
</script>

<template>
  <Primitive :as="as" :class="kbd({ variant, size, class: props.class })">
    <slot>
      {{ getKbdKey(value) }}
    </slot>
  </Primitive>
</template>

Theme

kbd.ts
ts
export default {
  base: '',
  variants: {
    variant: {
      solid: '',
      outline: '',
      subtle: '',
    },
    size: {
      sm: '',
      md: '',
      lg: '',
    },
  },
  defaultVariants: {
    variant: 'outline',
    size: 'md',
  } as const,
}
View Nuxt UI theme
kbd.ts
ts
export default {
  base: 'inline-flex items-center justify-center px-1 rounded-sm font-medium font-sans',
  variants: {
    variant: {
      solid: 'bg-inverted text-inverted',
      outline: 'bg-default text-highlighted ring ring-inset ring-accented',
      subtle: 'bg-elevated text-default ring ring-inset ring-accented',
    },
    size: {
      sm: 'h-4 min-w-[16px] text-[10px]',
      md: 'h-5 min-w-[20px] text-[11px]',
      lg: 'h-6 min-w-[24px] text-[12px]',
    },
  },
  defaultVariants: {
    variant: 'outline',
    size: 'md',
  } as const,
}

Test

To test this component, you can use the following test file:

Kbd.test.ts
ts
import type { RenderOptions } from '@testing-library/vue'
import Kdb from '@/ui/components/Kbd.vue'
import theme from '@/ui/theme/kbd'
import { render, screen } from '@testing-library/vue'
import { describe, expect, it } from 'vitest'

describe('kdb', () => {
  const sizes = Object.keys(theme.variants.size) as any
  const variants = Object.keys(theme.variants.variant) as any

  it.each<[string, RenderOptions<typeof Kdb>]>([
    // Props
    ['with value', { props: { value: 'K' } }],
    ...sizes.map((size: string) => [`with size ${size}`, { props: { value: 'K', size } }]),
    ...variants.map((variant: string) => [`with variant ${variant}`, { props: { value: 'K', variant } }]),
    ['with as', { props: { value: 'K', as: 'span' } }],
    ['with class', { props: { value: 'K', class: 'font-bold' } }],
    // Slots
    ['with default slot', { slots: { default: () => 'Default slot' } }],
  ])('renders %s correctly', (name, options) => {
    render(Kdb, {
      attrs: {
        'data-testid': 'kdb',
      },
      ...options,
    })

    expect(screen.getByTestId('kdb')).matchSnapshot()
  })
})
Kbd.test.ts
ts
import type { RenderOptions } from '@testing-library/vue'
import Kdb from '@/UI/Components/Kbd.vue'
import theme from '@/UI/Theme/kbd'
import { render, screen } from '@testing-library/vue'
import { describe, expect, it } from 'vitest'

describe('kdb', () => {
  const sizes = Object.keys(theme.variants.size) as any
  const variants = Object.keys(theme.variants.variant) as any

  it.each<[string, RenderOptions<typeof Kdb>]>([
    // Props
    ['with value', { props: { value: 'K' } }],
    ...sizes.map((size: string) => [`with size ${size}`, { props: { value: 'K', size } }]),
    ...variants.map((variant: string) => [`with variant ${variant}`, { props: { value: 'K', variant } }]),
    ['with as', { props: { value: 'K', as: 'span' } }],
    ['with class', { props: { value: 'K', class: 'font-bold' } }],
    // Slots
    ['with default slot', { slots: { default: () => 'Default slot' } }],
  ])('renders %s correctly', (name, options) => {
    render(Kdb, {
      attrs: {
        'data-testid': 'kdb',
      },
      ...options,
    })

    expect(screen.getByTestId('kdb')).matchSnapshot()
  })
})

Contributors

barbapapazes

Changelog