> ## Oymyakon DS index
> Fetch the full index at: https://super-dollop-pzmo65r.pages.github.io/llms.txt
> HTML preview: https://super-dollop-pzmo65r.pages.github.io/skeleton.html
> Source: specs/components/skeleton/skeleton.md, specs/components/skeleton/skeleton-a11y.md

---

# Skeleton

**Version:** 3.1.2  
**Status:** In progress  
**Figma:** v3.1.0

---

## Overview

Skeleton is a loading placeholder that replaces content while data is being fetched. It preserves the layout shape so the interface does not shift when real content appears.

Two presets are available:
- **Custom Skeleton** — free-form shape, no border-radius override. Used for images, avatars, squircles, and other custom blocks.
- **Text Skeleton** — fixed sizes tied to typography styles. Used to replace text elements. Border-radius and height are locked per style.

---

## Tokens

| Token | CSS variable | Light value | Dark value |
|---|---|---|---|
| On White | `--skeleton-on-white` | `#f2f1eb` | `#31302e` |
| White Overlay | `--skeleton-white-overlay` | `rgba(255,255,255,0.24)` | `rgba(255,255,255,0.24)` |
| Wave | `--skeleton-wave` | `rgba(255,255,255,0.56)` | `rgba(255,255,255,0.16)` |

**Usage rules:**
- Use `--skeleton-on-white` on `--background-primary` (white/light grey) backgrounds.
- Use `--skeleton-white-overlay` on colored, image, or dark backgrounds.

---

## Presets

### Custom Skeleton

Shape is defined by the parent component. No border-radius imposed. Used for: Squircle slots, image blocks, avatar circles, full-width cards.

```html
<div class="skeleton" data-ds-component="skeleton" data-ds-preset="custom">
  <div class="skeleton-animation">
    <div class="skeleton-gradient-walker"></div>
  </div>
</div>
```

### Text Skeleton

Fixed height and border-radius per typography style. Padding top/bottom separates the bar from the text line-height edges. Max 2 lines — if the design shows 3 lines, the skeleton shows 2.

```html
<div class="skeleton-text skeleton-text--main-body" data-ds-component="skeleton" data-ds-preset="text-main-body">
  <div class="skeleton" style="background: var(--skeleton-on-white);">
    <div class="skeleton-animation">
      <div class="skeleton-gradient-walker"></div>
    </div>
  </div>
</div>
```

---

## Text Skeleton size table

Each row maps a typography style to a fixed skeleton bar size.

| Typography style | Line-height | Skeleton height | Border-radius | Padding vertical |
|---|---|---|---|---|
| Promo Heading | `var(--sp-s44)` | 36px | `var(--sp-s12)` | `var(--sp-s4)` |
| Heading 1 | `var(--sp-s36)` | 30px | `var(--sp-s12)` | `var(--sp-s4)` |
| Heading 2 | `var(--sp-s28)` | 24px | `var(--sp-s8)` | `var(--sp-s2)` |
| Heading 3 | `var(--sp-s28)` | 20px | `var(--sp-s6)` | `var(--sp-s4)` |
| Heading 4 | `var(--sp-s20)` | 16px | `var(--sp-s6)` | `var(--sp-s2)` |
| Promo Body | `var(--sp-s28)` | 20px | `var(--sp-s8)` | `var(--sp-s4)` |
| Main Body | `var(--sp-s20)` | 16px | `var(--sp-s6)` | `var(--sp-s2)` |
| Compact Body | `var(--sp-s16)` | 12px | `var(--sp-s4)` | `var(--sp-s2)` |
| Main Link | `var(--sp-s20)` | 12px | `var(--sp-s4)` | `var(--sp-s4)` |
| Compact Link | `var(--sp-s16)` | 12px | `var(--sp-s4)` | `var(--sp-s2)` |
| Caption | `var(--sp-s16)` | 8px | `var(--sp-s2)` | `var(--sp-s4)` |

**Rules:**
1. Side margins within the text container are customizable.
2. Max 2 skeleton lines regardless of actual text line count.
3. Skeleton size does not change with increased font sizes.
4. Skeleton shape is fixed, regardless of the font set in use.

---

## CSS classes

| Class | Element | Purpose |
|---|---|---|
| `.skeleton` | Root block | Background color, overflow hidden, position relative |
| `.skeleton-animation` | Inner wrapper | Absolute inset, overflow hidden — clips the walker |
| `.skeleton-gradient-walker` | Animated element | Gradient overlay that sweeps left to right |
| `.skeleton-text` | Text preset wrapper | Padding top/bottom, contains `.skeleton` |
| `.skeleton-text--promo-heading` | Modifier | h=36px, radius=s12 |
| `.skeleton-text--heading1` | Modifier | h=30px, radius=s12 |
| `.skeleton-text--heading2` | Modifier | h=24px, radius=s8 |
| `.skeleton-text--heading3` | Modifier | h=20px, radius=s6 |
| `.skeleton-text--heading4` | Modifier | h=16px, radius=s6 |
| `.skeleton-text--promo-body` | Modifier | h=20px, radius=s8 |
| `.skeleton-text--main-body` | Modifier | h=16px, radius=s6 |
| `.skeleton-text--compact-body` | Modifier | h=12px, radius=s4 |
| `.skeleton-text--main-link` | Modifier | h=12px, radius=s4 |
| `.skeleton-text--compact-link` | Modifier | h=12px, radius=s4 |
| `.skeleton-text--caption` | Modifier | h=8px, radius=s2 |

---

## Animation

| Property | Value |
|---|---|
| Direction (LTR) | Left → right |
| Direction (RTL) | Right → left |
| Duration | 1500ms |
| Timing | `linear` |
| Iteration | `infinite` |
| Gradient | `transparent → --skeleton-wave → transparent` at 50% midpoint |
| Walker width | 150px |

```css
@keyframes skeleton-sweep {
  from { transform: translateX(-150px); }
  to   { transform: translateX(calc(100% + 150px)); }
}
```

**Reduced motion:** When `prefers-reduced-motion: reduce` — disable animation entirely. Keep background color.

**RTL:** Apply `dir="rtl"` on a parent container. The walker uses a separate `skeleton-sweep-rtl` keyframe that starts at the right edge and moves left.

---

## RTL

| Situation | Rule |
|---|---|
| LTR default | Wave sweeps left → right |
| RTL locale | Wave sweeps right → left |
| Implementation | `[dir="rtl"] .skeleton-gradient-walker { animation-name: skeleton-sweep-rtl; }` |

---

## Changelog

| Version | Date | Change |
|---|---|---|
| 3.1.2 | 2026-09-10 | New text size `caption` (`.skeleton-text--caption`): 8px bar, radius `var(--sp-s2)`, `var(--sp-s4)` vertical padding — shipped for the ServiceCard skeleton title line. |
| 3.1.1 | 2026-08-04 | `.skeleton` and `.skeleton-text` now carry `pointer-events: none` and `user-select: none`. `skeleton-a11y.md` § Focus had specified both since 3.1.0 and neither was in the CSS — a placeholder could be hovered and its empty text selected. |
| 3.1.0 | 2026-05-25 | Initial spec transferred from Figma. Two presets (Custom, Text), 11 typography sizes, wave animation 1500ms, RTL, a11y. |

---

# Skeleton — Accessibility

**Component:** Skeleton  
**Version:** 3.1.0  
**Spec:** [`skeleton.md`](https://super-dollop-pzmo65r.pages.github.io/skeleton.md)

---

## Role and ARIA

| Attribute | Value | Where |
|---|---|---|
| `role` | `"status"` | Root skeleton container (or wrapping region) |
| `aria-label` | `"Loading"` | Root skeleton container |
| `aria-live` | `"polite"` | Root skeleton container |
| `aria-busy` | `"true"` | The content region being loaded |

When the real content replaces the skeleton:
- Remove `aria-busy="true"` (or set to `"false"`)
- Remove the skeleton from the DOM

**Screen readers** should announce "Loading" once when the skeleton appears. The polite live region prevents interrupting ongoing speech.

---

## Focus

Skeleton elements are not interactive. They must not receive keyboard focus.

```css
.skeleton, .skeleton-text {
  pointer-events: none;
  user-select: none;
}
```

Do not add `tabindex` to any skeleton element.

---

## Reduced motion

When `prefers-reduced-motion: reduce` is active — disable the wave animation entirely. The skeleton background color is sufficient to communicate the loading state.

```css
@media (prefers-reduced-motion: reduce) {
  .skeleton-gradient-walker {
    animation: none;
  }
}
```

---

## Contrast

| Token | Light value | Contrast vs background |
|---|---|---|
| `--skeleton-on-white` on `--background-primary` | `#f2f1eb` on `#ffffff` | Low — intentional (decorative placeholder) |
| `--skeleton-white-overlay` on colored bg | `rgba(255,255,255,0.24)` | Low — intentional |

Skeleton is a decorative loading state, not meaningful content. Low contrast is acceptable per WCAG 1.4.3 exception for decorative elements. The `aria-label="Loading"` communicates purpose to assistive technology.

---

## Example markup

```html
<!-- Content region while loading -->
<div role="status" aria-label="Loading" aria-live="polite" aria-busy="true">

  <!-- Text skeleton replacing a heading -->
  <div class="skeleton-text skeleton-text--heading2">
    <div class="skeleton" style="background: var(--skeleton-on-white);">
      <div class="skeleton-animation">
        <div class="skeleton-gradient-walker"></div>
      </div>
    </div>
  </div>

  <!-- Custom skeleton replacing an image -->
  <div class="skeleton" style="width: 80px; height: 80px; border-radius: var(--sp-s20); background: var(--skeleton-on-white);" data-ds-component="skeleton" data-ds-preset="custom">
    <div class="skeleton-animation">
      <div class="skeleton-gradient-walker"></div>
    </div>
  </div>

</div>
```
