Skeleton

Loading placeholder that matches the shape of the content it's replacing. Settld skeletons use a warm-tinted shimmer — never gray — so they feel native to the cream palette. Shapes: text line, avatar circle, card rect, amount rect, button pill, chip pill. Shimmer sweeps left to right at 1.5s with a subtle butter-tinted peak.

Variants

Text

Heading

Avatar

Amount

Card

Button & chip

Text — use varying widths (50–90%) for lines in a text block. Never use the same width — it reads as a single word, not a natural line break pattern.

Avatar — circle shapes (50% border-radius) for member avatars and profile pictures.

Amount — wider and taller rects with a slightly larger border-radius. Used when replacing the primary amount display in expense cards and the settle-up header.

Card — full-width rect for expense rows, group cards, and summary cards. Height matches the real card's height.

Button / chip — pill shape for loading CTA buttons and person chips.

Sizes

VariantModifierDimensionsMatches
Text.skeleton--text-sm11px tallCaption / footnote text
Text.skeleton--text14px tallBody text (default)
Text.skeleton--text-lg18px tallLarge body / subheadings
Heading.skeleton--heading24px tallSection headings, card titles
Avatar.skeleton--avatar-sm28×28pxPerson chip avatar
Avatar.skeleton--avatar40×40pxList item avatar
Avatar.skeleton--avatar-lg56×56pxProfile screen avatar
Amount.skeleton--amount32px tall, 120px wideExpense card amount
Amount.skeleton--amount-lg44px tall, 160px wideSettle-up header amount
Card.skeleton--card80px tall, full widthExpense row card
Card.skeleton--card-lg120px tall, full widthGroup summary card
Chip.skeleton--chip36px tall, 80px widePerson chip
Button.skeleton--btn44px tall, 120px wideCTA button
Button.skeleton--btn-block44px tall, 100% wideFull-width action button

States

Loading (shimmer active)

Reduced motion (static, no shimmer)

StateVisualWhen
LoadingWarm shimmer sweeping left to rightData fetch in progress
Reduced motionStatic warm tint, no animationprefers-reduced-motion: reduce
ResolvedSkeleton removed, real content fades inData loaded

On dark surfaces

On dark surfaces, the skeleton base uses --dark-border and the shimmer is a lighter translucent white wash. The warm butter tint in the shimmer peak is more subtle on dark — just a hint of warmth, not full butter yellow.

Composition — expense list

Compose shape primitives to match the real layout. The expense list row uses an avatar circle, two text lines (name + date/group), and an amount rect. Vary widths on text lines — never repeat the same width twice in a row.

Composition — group header

Anatomy

Base (--s-deep bg)
Shimmer (::after, warm gradient)
PartElementNotes
Basediv.skeleton--s-deep background, overflow: hidden, shape via modifier class.
Shimmer::after pseudo-elementAbsolute inset. Warm gradient sweep. 1.5s ease infinite. Paused on prefers-reduced-motion.

Usage

Do Show skeletons immediately when a fetch begins — before the loading spinner threshold. Match the skeleton layout exactly to the real content shape. Stagger shimmer delays by 50–100ms between rows for a natural wave effect.
Don't Don't show skeletons for operations faster than 300ms — the flash is worse than nothing. Don't use generic gray rectangles — the warm tint is part of the Settld feel. Don't mix skeleton loading and spinner loading for the same surface.
Do Vary text skeleton widths within a block (50–90%). Use wider amounts for INR values since they're longer. Show the correct number of skeleton rows — if the real list will have 5 items, show 5 skeleton rows, not 3.
Don't Don't keep skeletons visible after data has loaded — swap them out immediately. Don't use skeletons for error states — those get empty state or error UI. Don't animate skeleton on first paint — defer to next frame.

Accessibility

ARIA Wrap the skeleton block in a container with aria-busy="true" while loading. Once data loads and skeletons are replaced, set aria-busy="false". Screen readers will announce "busy" and then "not busy." Add an aria-label like "Loading expenses" to the container.
Motion The shimmer runs indefinitely. Always include the prefers-reduced-motion override — users with vestibular disorders find continuously sweeping animations distressing. The static fallback (background: rgba(251,248,240,0.2) on ::after) still communicates "loading" without motion.
Tab order Skeletons are decorative — they should not be focusable. Use aria-hidden="true" on individual skeleton elements if they're inside a container that isn't aria-busy.

Code

HTML

<!-- Loading container -->
<div aria-busy="true" aria-label="Loading expenses">

  <!-- Expense row skeleton -->
  <div class="skeleton-row" aria-hidden="true">
    <div class="skeleton skeleton--avatar"></div>
    <div class="skeleton-stack" style="flex:1">
      <div class="skeleton skeleton--text" style="width:65%"></div>
      <div class="skeleton skeleton--text skeleton--text-sm" style="width:40%"></div>
    </div>
    <div class="skeleton skeleton--amount"></div>
  </div>

  <!-- Repeat for expected number of rows -->
</div>

<!-- Full-width button skeleton -->
<div class="skeleton skeleton--btn-block" aria-hidden="true"></div>

CSS classes

ClassPurpose
.skeletonBase — warm bg, overflow hidden, shimmer ::after
.skeleton--text14px tall text line
.skeleton--text-sm11px tall small text line
.skeleton--text-lg18px tall large text line
.skeleton--heading24px tall heading
.skeleton--avatar40px circle
.skeleton--avatar-sm28px circle
.skeleton--avatar-lg56px circle
.skeleton--amount32×120px wider rect
.skeleton--amount-lg44×160px large amount
.skeleton--card80px tall full-width card
.skeleton--card-lg120px tall full-width card
.skeleton--chip36×80px pill chip
.skeleton--btn44×120px pill button
.skeleton--btn-block44px tall, full-width pill
.skeleton-rowFlex row with 12px gap
.skeleton-stackFlex column with 8px gap

Design tokens used

TokenValueRole
--s-deep#E8DEC5Skeleton base color (warm, not gray)
--dark-border#3D3630Dark surface skeleton base
--butter#F4C94EWarm tint in shimmer peak
--r-xs6pxText/heading border radius
--r-sm10pxAmount border radius
--r-md14pxCard border radius
--r-pill999pxChip/button border radius