Theme
On this page (10)
Component Skill — components/kbd/component-skill.md

Native basis

<kbd class="kbd"> per key; <kbd class="kbd-group"> around several for a combination - the outer element is the chord and keeps none of the key styling.

Web Platform APIs

<kbd>color-mix()prefers-contrastforced-colors

Classes

.kbd.kbd-group

Data attributes

data-size (sm, lg) on a key; data-separator on a group draws a + between its keys; --kbd-size for any other key size.

§Keys

One <kbd class='kbd'> per key - write the symbol you want read: ⌘, ⇧, ⌥, Ctrl, Enter, Esc.

§Combination

A chord is a <kbd class='kbd-group'> holding one <kbd class='kbd'> per key - the nesting the HTML spec prescribes; the outer element is not a key itself.

§With a separator

data-separator on the group draws a + in the gap between keys - outside the key boxes, never inside one.

§Sizes

data-size sm (inside buttons and menu items) · default · lg - or any size through --kbd-size.

§In a control

Inside a button the key drops its own surface and borrows the button's text colour through color-mix - one rule for every variant, legible on the dark primary button and the outline one alike.

§In prose

Keys sit on the text baseline, so they read inside a sentence.

§In a menu

Shortcuts in dropdown items blend in the same way - the key takes the item's colour, including the highlighted and destructive states.

§In a tooltip

On a tooltip's inverted surface the key borrows the tooltip's colours too - hover or focus the button.

§Decorative shortcuts

No ARIA: <kbd> carries the meaning and its text is the label. When the shortcut only decorates a control that already names its action, hide it with aria-hidden.

§CSS view file

/* -- Kbd ---------------------------------------------------------- */
/* Keyboard keys. Built on the native <kbd> element; a combination is a  */
/* <kbd> holding one <kbd> per key, which is what the HTML spec          */
/* prescribes — so the markup already says "these keys together".        */
@layer components {
  .kbd {
    --kbd-size: 1.375rem;
    display: inline-grid;
    place-items: center;
    min-width: var(--kbd-size);
    height: var(--kbd-size);
    padding-inline: 0.375rem;
    border: 1px solid var(--border);
    /* the bottom edge is thicker, which is what reads as a physical key */
    border-bottom-width: 2px;
    border-radius: var(--radius-sm);
    background: var(--muted);
    color: var(--muted-foreground);
    font-family: var(--font-mono);
    font-size: 0.75rem;
    font-weight: 500;
    line-height: 1;
    white-space: nowrap;
    vertical-align: middle;
    &[data-size="sm"] {
      --kbd-size: 1.125rem;
      padding-inline: 0.25rem;
      font-size: 0.6875rem;
    }
    &[data-size="lg"] {
      --kbd-size: 1.75rem;
      padding-inline: 0.5rem;
      font-size: 0.875rem;
    }
  }
  /* A key combination: an outer <kbd> holding one <kbd> per key. The outer
     element is not a key itself, so it keeps none of the key styling. */
  .kbd-group {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    vertical-align: middle;
    font-family: inherit;
    background: none;
    border: 0;
    padding: 0;
  }
  /* Optional "+" between keys. Absolutely placed in the gap so it never sits
     inside a key's box. */
  .kbd-group[data-separator] {
    gap: 0.875rem;
    & > .kbd + .kbd {
      position: relative;
      &::before {
        content: "+";
        position: absolute;
        inset-inline-start: -0.625rem;
        color: var(--muted-foreground);
        font-size: 0.6875rem;
        line-height: 1;
        pointer-events: none;
      }
    }
  }
  /* Inside a control - or on a tooltip's inverted surface - the key should
     not out-shout its label: it drops its own surface and borrows the
     text colour around it. */
  .btn .kbd,
  .command-item .kbd,
  .dropdown-item .kbd,
  .menubar-trigger .kbd,
  .tooltip .kbd {
    background: transparent;
    border-color: color-mix(in oklch, currentColor 25%, transparent);
    color: inherit;
    opacity: 0.75;
  }
}
@media (prefers-reduced-motion: reduce) {
  @layer components {
    .kbd,
    .kbd *,
    .kbd::before,
    .kbd::after,
    .kbd *::before,
    .kbd *::after,
    .kbd-group,
    .kbd-group::before,
    .kbd-group::after {
      transition: none;
      animation: none;
    }
  }
}
@media (prefers-contrast: more) {
  @layer components {
    .kbd {
      border-color: var(--foreground);
      color: var(--foreground);
    }
  }
}
@media (forced-colors: active) {
  @layer components {
    .kbd {
      border-color: ButtonText;
      color: ButtonText;
      background: ButtonFace;
    }
    .kbd-group[data-separator] > .kbd + .kbd::before {
      color: ButtonText;
    }
  }
}

Comments, ideas or improvements? Edit this page's source on GitHub