Display

The small pieces: avatar, breadcrumb, progress, skeleton, tooltip. None of them introduce a new idea, which is why they are one page rather than five.

Reference

sk-avatar

An image if there is one, initials if there is not - never a generic silhouette, because initials tell two rows apart at a glance and a grey person icon does not. Initials are set at 40% of the circle so they hold their proportion at every size.

<span class="sk-avatar" data-size="sm">
  <img src="…" alt="Ada Lovelace">
</span>
AttributeTypeDefaultNotes
data-size"xs" | "sm" | "lg" | "xl"md1.5 / 2 / 2.5 / 3 / 4rem
data-shape"square"circleConventional for organisations and projects, as opposed to people.
data-overflow"true"-The "+3" chip that ends a stack.
sk-avatar-groupwrapper-Overlaps the stack. The ring is a shadow, not a border, so it does not eat into the image.

sk-breadcrumb-list

Separators are real elements marked aria-hidden, not CSS ::after content. Generated content is announced by several screen readers - turning a trail into "Home slash Settings slash Billing" - and it cannot be hidden from the accessibility tree, because you cannot put an attribute on a pseudo-element.

<nav aria-label="Breadcrumb">
  <ol class="sk-breadcrumb-list">
    <li class="sk-breadcrumb-item"><a class="sk-breadcrumb-link" href="/">Home</a></li>
    <li class="sk-breadcrumb-item">
      <span class="sk-breadcrumb-separator" aria-hidden="true">…</span>
      <span class="sk-breadcrumb-current" aria-current="page">Billing</span>
    </li>
  </ol>
</nav>
AttributeTypeDefaultNotes
aria-current"page"-On the last crumb, which is where you are and therefore not a link.

sk-progress

A native <progress>: it carries its own role, value semantics and indeterminate state, none of which a div with a width has. Styling costs three pseudo-elements that cannot share a rule - an unknown selector invalidates the whole list, so ::-webkit- and ::-moz- are written separately.

<progress class="sk-progress" value="40" max="100" aria-labelledby="x"></progress>
AttributeTypeDefaultNotes
valuenumber-Omit entirely for the indeterminate state - a sliding band rather than a filled bar, so it cannot be misread as "almost done".
data-status"success" | "warning" | "danger"accent
data-size"sm" | "lg"md4 / 8 / 12px
aria-labelledbyid-A bare progress bar announces a number with no subject.

sk-skeleton

Always aria-hidden. A screen reader user gets nothing from "grey box, grey box, grey box" - the real announcement belongs in the live region that reports the result. The shimmer is a highlight over a flat base, so a skeleton with animation suppressed is still visibly a placeholder rather than blank.

<span class="sk-skeleton" data-shape="text" aria-hidden="true"></span>
<span class="sk-skeleton-lines" aria-hidden="true">…</span>
AttributeTypeDefaultNotes
data-shape"text" | "circle" | "block"barSized in em, so a text skeleton is the height of the line it stands in for.
data-size"sm" | "md" | "lg" | "xl"-For a circle standing in for an avatar, which should be the avatar's size and not a line of text.
data-busy"true"-Shimmers. Opt-in, matching Dot: a placeholder still pulsing after the request died is a lie the interface keeps telling. Turn it off when the wait ends, however it ends.
data-motion"essential"-Emitted with data-busy. Under reduced motion the sweep becomes an opacity pulse - a sweep across a screenful of blocks is what the preference is for, but freezing it makes a loading screen indistinguishable from a broken one.

sk-tooltip

Shown by :hover and :focus-within, so it works on keyboard focus as well as pointer. It lives in a positioned wrapper rather than the top layer - a popover would escape ancestor clipping, but showing one needs a script, since popovertarget is click-only and there is no declarative hover trigger. The consequence, stated rather than hidden: a tooltip inside a scroll container is clipped by it.

<span class="sk-tooltip-wrap">
  <button class="sk-button" aria-describedby="tip">…</button>
  <span class="sk-tooltip" id="tip" role="tooltip">Archive</span>
</span>
AttributeTypeDefaultNotes
data-side"bottom"top
aria-describedbytooltip id-On the control, not the tooltip. Without it the tooltip is drawn but never announced.

sk-input[list] + datalist

Native suggestions, zero JavaScript. The browser supplies filtering, keyboard handling, scrolling and the mobile presentation - all of which a scripted combobox reimplements and usually gets partly wrong. A datalist is a hint, not a constraint: use a Select when the answer must come from the list.

<input class="sk-input" id="country" list="country-list" autocomplete="off">
<datalist id="country-list">
  <option value="New Zealand"></option>
</datalist>
AttributeTypeDefaultNotes
listdatalist id-No styled options, no custom option rendering, no multi-select, no async loading. Those need the scripted ARIA combobox - the first component here that would require shipping JavaScript.

Examples

Avatar

Image, initials, and the sizes.

Ada Lovelace Ada Lovelace Ada Lovelace Ada Lovelace Ada Lovelace
Ada Lovelace Grace Hopper Alan Turing Katherine Johnson Soft Kiwi Games
Show markup
<div class="sk-flex sk-flex-col sk-gap-5">
  <div class="sk-flex sk-flex-wrap sk-items-center sk-gap-3">
    <span class="sk-avatar" data-size="xs">
      <span aria-hidden="true">
        AL
      </span>
      <span class="sk-visually-hidden">
        Ada Lovelace
      </span>
    </span>
    <span class="sk-avatar" data-size="sm">
      <span aria-hidden="true">
        AL
      </span>
      <span class="sk-visually-hidden">
        Ada Lovelace
      </span>
    </span>
    <span class="sk-avatar">
      <span aria-hidden="true">
        AL
      </span>
      <span class="sk-visually-hidden">
        Ada Lovelace
      </span>
    </span>
    <span class="sk-avatar" data-size="lg">
      <span aria-hidden="true">
        AL
      </span>
      <span class="sk-visually-hidden">
        Ada Lovelace
      </span>
    </span>
    <span class="sk-avatar" data-size="xl">
      <span aria-hidden="true">
        AL
      </span>
      <span class="sk-visually-hidden">
        Ada Lovelace
      </span>
    </span>
  </div>
  <div class="sk-flex sk-flex-wrap sk-items-center sk-gap-3">
    <span class="sk-avatar">
      <span aria-hidden="true">
        AL
      </span>
      <span class="sk-visually-hidden">
        Ada Lovelace
      </span>
    </span>
    <span class="sk-avatar">
      <span aria-hidden="true">
        GH
      </span>
      <span class="sk-visually-hidden">
        Grace Hopper
      </span>
    </span>
    <span class="sk-avatar">
      <span aria-hidden="true">
        AT
      </span>
      <span class="sk-visually-hidden">
        Alan Turing
      </span>
    </span>
    <span class="sk-avatar">
      <span aria-hidden="true">
        KJ
      </span>
      <span class="sk-visually-hidden">
        Katherine Johnson
      </span>
    </span>
    <span class="sk-avatar" data-shape="square">
      <span aria-hidden="true">
        SG
      </span>
      <span class="sk-visually-hidden">
        Soft Kiwi Games
      </span>
    </span>
  </div>
</div>

Avatar group

The ring uses the surface colour, so a stack reads correctly on whatever it sits on.

Ada Lovelace Grace Hopper Alan Turing Katherine Johnson 3 more
Show markup
<span class="sk-avatar-group" role="group" aria-label="Team members">
  <span class="sk-avatar" data-size="sm">
    <span aria-hidden="true">
      AL
    </span>
    <span class="sk-visually-hidden">
      Ada Lovelace
    </span>
  </span>
  <span class="sk-avatar" data-size="sm">
    <span aria-hidden="true">
      GH
    </span>
    <span class="sk-visually-hidden">
      Grace Hopper
    </span>
  </span>
  <span class="sk-avatar" data-size="sm">
    <span aria-hidden="true">
      AT
    </span>
    <span class="sk-visually-hidden">
      Alan Turing
    </span>
  </span>
  <span class="sk-avatar" data-size="sm">
    <span aria-hidden="true">
      KJ
    </span>
    <span class="sk-visually-hidden">
      Katherine Johnson
    </span>
  </span>
  <span class="sk-avatar" data-overflow="true" data-size="sm">
    <span aria-hidden="true">
      +3
    </span>
    <span class="sk-visually-hidden">
      3 more
    </span>
  </span>
</span>

Breadcrumb

Show markup
<div class="sk-flex sk-flex-col sk-gap-4">
  <nav aria-label="Breadcrumb">
    <ol class="sk-breadcrumb-list">
      <li class="sk-breadcrumb-item">
        <a class="sk-breadcrumb-link" href="/">
          Home
        </a>
      </li>
      <li class="sk-breadcrumb-item">
        <span class="sk-breadcrumb-separator" aria-hidden="true">
          <svg class="sk-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
            <path d="m9 18 6-6-6-6">
            </path>
          </svg>
        </span>
        <a class="sk-breadcrumb-link" href="/settings">
          Settings
        </a>
      </li>
      <li class="sk-breadcrumb-item">
        <span class="sk-breadcrumb-separator" aria-hidden="true">
          <svg class="sk-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
            <path d="m9 18 6-6-6-6">
            </path>
          </svg>
        </span>
        <span class="sk-breadcrumb-current" aria-current="page">
          Billing
        </span>
      </li>
    </ol>
  </nav>
  <nav aria-label="Project">
    <ol class="sk-breadcrumb-list">
      <li class="sk-breadcrumb-item">
        <a class="sk-breadcrumb-link" href="/">
          Home
        </a>
      </li>
      <li class="sk-breadcrumb-item">
        <span class="sk-breadcrumb-separator" aria-hidden="true">
          <svg class="sk-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
            <path d="m9 18 6-6-6-6">
            </path>
          </svg>
        </span>
        <a class="sk-breadcrumb-link" href="/p">
          Projects
        </a>
      </li>
      <li class="sk-breadcrumb-item">
        <span class="sk-breadcrumb-separator" aria-hidden="true">
          <svg class="sk-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
            <path d="m9 18 6-6-6-6">
            </path>
          </svg>
        </span>
        <a class="sk-breadcrumb-link" href="/p/1">
          Kiwi Kart
        </a>
      </li>
      <li class="sk-breadcrumb-item">
        <span class="sk-breadcrumb-separator" aria-hidden="true">
          <svg class="sk-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
            <path d="m9 18 6-6-6-6">
            </path>
          </svg>
        </span>
        <a class="sk-breadcrumb-link" href="/p/1/b">
          Builds
        </a>
      </li>
      <li class="sk-breadcrumb-item">
        <span class="sk-breadcrumb-separator" aria-hidden="true">
          <svg class="sk-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
            <path d="m9 18 6-6-6-6">
            </path>
          </svg>
        </span>
        <span class="sk-breadcrumb-current" aria-current="page">
          Build 412
        </span>
      </li>
    </ol>
  </nav>
</div>

Progress

Uploading 40%
Files 3 of 12 files
Storage 92%
Complete 100%
60%
Show markup
<div class="sk-flex sk-flex-col sk-gap-5">
  <div class="sk-progress-field">
    <div class="sk-progress-head">
      <span id="Uploading-progress-label">
        Uploading
      </span>
      <span class="sk-progress-value">
        40%
      </span>
    </div>
    <progress class="sk-progress" value="40" max="100" aria-labelledby="Uploading-progress-label">
    </progress>
  </div>
  <div class="sk-progress-field">
    <div class="sk-progress-head">
      <span id="Files-progress-label">
        Files
      </span>
      <span class="sk-progress-value">
        3 of 12 files
      </span>
    </div>
    <progress class="sk-progress" value="3" max="12" aria-labelledby="Files-progress-label">
    </progress>
  </div>
  <div class="sk-progress-field">
    <div class="sk-progress-head">
      <span id="Storage-progress-label">
        Storage
      </span>
      <span class="sk-progress-value">
        92%
      </span>
    </div>
    <progress class="sk-progress" value="92" max="100" data-status="warning" aria-labelledby="Storage-progress-label">
    </progress>
  </div>
  <div class="sk-progress-field">
    <div class="sk-progress-head">
      <span id="Complete-progress-label">
        Complete
      </span>
      <span class="sk-progress-value">
        100%
      </span>
    </div>
    <progress class="sk-progress" value="100" max="100" data-status="success" aria-labelledby="Complete-progress-label">
    </progress>
  </div>
  <div class="sk-progress-field">
    <div class="sk-progress-head">
      <span class="sk-progress-value">
        60%
      </span>
    </div>
    <progress class="sk-progress" value="60" max="100" data-size="sm">
    </progress>
  </div>
</div>

Indeterminate

No value attribute. A band that slides rather than a bar that fills.

Working
Show markup
<div class="sk-flex sk-flex-col sk-gap-4">
  <div class="sk-progress-field">
    <div class="sk-progress-head">
      <span id="Working-progress-label">
        Working
      </span>
    </div>
    <progress class="sk-progress" max="100" aria-labelledby="Working-progress-label">
    </progress>
  </div>
  <progress class="sk-progress" max="100" data-size="lg">
  </progress>
</div>

Skeleton

Busy and static side by side. The last line of the paragraph is short, which is most of what makes it read as text rather than as bars.

Busy: true - waiting on something
static - an empty slot, or a request that has stopped
Show markup
<div class="sk-flex sk-flex-col sk-gap-5">
  <div class="sk-flex sk-flex-col sk-gap-2">
    <code class="sink-caption">
      Busy: true - waiting on something
    </code>
    <div class="sk-card">
      <div class="sk-card-body">
        <div class="sk-flex sk-gap-3">
          <span class="sk-skeleton" aria-hidden="true" data-shape="circle" data-size="lg" data-busy="true" data-motion="essential">
          </span>
          <div class="sk-flex sk-flex-col sk-gap-2 sk-flex-1">
            <span class="sk-skeleton-lines" aria-hidden="true">
              <span class="sk-skeleton" data-shape="text" data-busy="true" data-motion="essential">
              </span>
              <span class="sk-skeleton" data-shape="text" data-busy="true" data-motion="essential">
              </span>
              <span class="sk-skeleton" data-shape="text" data-busy="true" data-motion="essential">
              </span>
            </span>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="sk-flex sk-flex-col sk-gap-2">
    <code class="sink-caption">
      static - an empty slot, or a request that has stopped
    </code>
    <div class="sk-card">
      <div class="sk-card-body">
        <div class="sk-flex sk-gap-3">
          <span class="sk-skeleton" aria-hidden="true" data-shape="circle" data-size="lg">
          </span>
          <div class="sk-flex sk-flex-col sk-gap-2 sk-flex-1">
            <span class="sk-skeleton-lines" aria-hidden="true">
              <span class="sk-skeleton" data-shape="text">
              </span>
              <span class="sk-skeleton" data-shape="text">
              </span>
              <span class="sk-skeleton" data-shape="text">
              </span>
            </span>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="sk-card">
    <span class="sk-skeleton" aria-hidden="true" data-shape="block" data-busy="true" data-motion="essential">
    </span>
    <div class="sk-card-body">
      <span class="sk-skeleton-lines" aria-hidden="true">
        <span class="sk-skeleton" data-shape="text" data-busy="true" data-motion="essential">
        </span>
        <span class="sk-skeleton" data-shape="text" data-busy="true" data-motion="essential">
        </span>
      </span>
    </div>
  </div>
</div>

Tooltip

Hover or tab to them. The keyboard case is the one most tooltip implementations forget.

Open in a new tab Delete permanently This one opens downward
Show markup
<div class="sk-flex sk-flex-wrap sk-items-center sk-gap-5">
  <span class="sk-tooltip-wrap">
    <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-describedby="tip-archive" aria-label="Open in a new tab">
      <svg class="sk-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
        <path d="M14 4h6v6">
        </path>
        <path d="M20 4 11 13">
        </path>
        <path d="M18 14v4a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4">
        </path>
      </svg>
    </button>
    <span class="sk-tooltip" id="tip-archive" role="tooltip">
      Open in a new tab
    </span>
  </span>
  <span class="sk-tooltip-wrap">
    <button class="sk-button" type="button" data-variant="danger" data-size="md" aria-describedby="tip-delete" aria-label="Delete permanently">
      <svg class="sk-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
        <path d="M4 7h16">
        </path>
        <path d="M10 11v6">
        </path>
        <path d="M14 11v6">
        </path>
        <path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12">
        </path>
        <path d="M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2">
        </path>
      </svg>
    </button>
    <span class="sk-tooltip" id="tip-delete" role="tooltip">
      Delete permanently
    </span>
  </span>
  <span class="sk-tooltip-wrap">
    <button class="sk-button" type="button" data-variant="quiet" data-size="md" aria-describedby="tip-below">
      Below
    </button>
    <span class="sk-tooltip" id="tip-below" role="tooltip" data-side="bottom">
      This one opens downward
    </span>
  </span>
</div>

Combobox

Native suggestions. Type a letter.

Suggestions only - any value is allowed.

Show markup
<div class="sk-flex sk-flex-col sk-gap-4">
  <div class="sk-field">
    <label class="sk-label" for="country">
      Country
    </label>
    <input class="sk-input" id="country" name="country" type="text" list="country-list" value="" placeholder="Start typing" autocomplete="off" data-size="md" aria-invalid="false" aria-describedby="country-hint">
    <datalist id="country-list">
      <option value="New Zealand">
      </option>
      <option value="Netherlands">
      </option>
      <option value="Norway">
      </option>
      <option value="Nigeria">
      </option>
      <option value="Nepal">
      </option>
    </datalist>
    <p class="sk-hint" id="country-hint">
      Suggestions only - any value is allowed.
    </p>
  </div>
</div>