Skip to content
  1. Keyline UI
  2. Skeleton
Keyline UIComponents
  • Get started
    • Installation
    • Theming
    • Modes
    • Configuration
    • Composition
  • Actions
    • Button
    • Button group
    • Split button
    • Toggle
    • Toggle group
    • Toolbar
    • Menu
    • Menubar
    • Context menu
  • Inputs
    • Field and form
    • Checkbox
    • Checkbox group
    • Radio group
    • Switch
    • Slider
    • Segmented
    • Select
    • Native select
    • Combobox
    • Multiselect
    • Number field
    • OTP field
    • Time field
    • Date picker and calendar
    • Date range
  • Data
    • Table
    • Chart
    • Stat and meter
    • Status and badge
    • Progress
    • Skeleton
    • Empty
    • Avatar
    • Kbd
    • Marker
  • Navigation
    • Tabs
    • Breadcrumb
    • Pagination
    • Nav
    • Navigation menu
    • Command menu
  • Overlays
    • Dialog
    • Alert dialog
    • Drawer
    • Sheet
    • Popover
    • Hover card
    • Tooltip
    • Toast
    • Notice
  • Layout
    • App shell
    • Sidebar
    • Panel
    • Resizable
    • Scroll area
    • Accordion
    • Collapsible
    • Carousel
    • Inspector
    • Item
    • Direction
  • Conversation
    • Message
    • Bubble
    • Message scroller
    • Attachment
    • Questionnaire
  • Blocks
    • Shipments

Skeleton

A placeholder the size of the content on its way, so nothing moves when it lands. A skeleton is hidden from screen readers; the group around it says once what is loading. Under reduced motion it holds still.

Loading the live example…

"use client"

import { useEffect, useRef, useState } from "react"
import { Button } from "@/components/keyline/ui/button"
import { Panel, PanelBody, PanelHeader } from "@/components/keyline/ui/panel"
import { Skeleton, SkeletonGroup } from "@/components/keyline/ui/skeleton"
import { Stat } from "@/components/keyline/ui/stat"

export function ClientCard() {
  const [busy, setBusy] = useState(true)
  const timer = useRef<ReturnType<typeof setTimeout>>(undefined)
  const load = () => {
    setBusy(true)
    clearTimeout(timer.current)
    timer.current = setTimeout(() => setBusy(false), 1400)
  }
  useEffect(() => {
    load()
    return () => clearTimeout(timer.current)
  }, [])

  return (
    <div className="grid max-w-[520px] gap-4">
      <SkeletonGroup label="Loading Lackawanna County" busy={busy}>
        <Panel>
          <PanelHeader title="Lackawanna County" code="ACCT-0418" />
          <PanelBody flush>
            <div className="@container">
              <div className="grid divide-y divide-kl-line @xs:grid-cols-2 @xs:divide-x @xs:divide-y-0">
                <Stat label="Orders this year" value={busy ? <Skeleton shape="value" width="2ch" /> : 31} />
                <Stat label="Spent" value={busy ? <Skeleton shape="value" width="6ch" /> : "$48,200"} />
              </div>
            </div>
          </PanelBody>
          <PanelBody className="grid gap-2 border-t border-kl-line">
            <span className="text-[13px] font-semibold text-kl-ink-2">Notes from Dwight</span>
            {busy ? (
              <Skeleton lines={3} />
            ) : (
              <p className="m-0 text-[14px] leading-relaxed">
                Buys in bulk every quarter. The new purchasing officer prefers phone calls to email and wants the delivery on the loading dock, not at reception.
              </p>
            )}
          </PanelBody>
        </Panel>
      </SkeletonGroup>
      <Button className="justify-self-start" disabled={busy} onClick={load}>
        Reload
      </Button>
    </div>
  )
}

Installation

npx shadcn@latest add @keyline-ui/skeleton

Run it where your components.json is. The first time, map the @keyline-ui registry: one command, in Installation.

Usage

import { Skeleton, SkeletonGroup } from "@/components/keyline/ui/skeleton"
<SkeletonGroup label="Loading Lackawanna County" busy={loading}>
  {loading ? <Skeleton lines={3} /> : <p>{notes}</p>}
</SkeletonGroup>

Props

PropTypeDefaultDescription
labelstringDefault —On SkeletonGroup: announced once while busy.
busybooleanDefault trueOn SkeletonGroup: marks the region busy. Set it to false when the content lands.
shape"line" | "block" | "value" | "control"Default "line"A run of text, an area, a figure in the count voice, or a control's frame.
linesnumberDefault 1For line: stacks several, the last one shorter.
widthCSS lengthDefault by shapeSize it to the real content.
heightCSS lengthDefault 96pxFor block only.

Lines take a fixed pattern of widths, so a skeleton looks the same on every load.

Sheet 37 of 73PreviousProgressNextEmpty
On this page