Keyboard Key
A kbd element to display a keyboard key.
Demo
A
Related Composables
This requires the following composables to be installed:
Related Theme
This requires the following theme to be installed:
Component
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>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
ts
export default {
base: '',
variants: {
variant: {
solid: '',
outline: '',
subtle: '',
},
size: {
sm: '',
md: '',
lg: '',
},
},
defaultVariants: {
variant: 'outline',
size: 'md',
} as const,
}View Nuxt UI theme
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:
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()
})
})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()
})
})