mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-22 07:38:12 +00:00
docs: UX-013 — design system documentation (#89827)
* docs: UX-013 — shared design system documentation * docs: align design system docs with source tokens
This commit is contained in:
20
ui/docs/design-system/README.md
Normal file
20
ui/docs/design-system/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# OpenClaw Design System
|
||||
|
||||
OpenClaw's visual language is a **dark-first, glass-surface system** built around a deep charcoal base (`#0e1015`), a punchy signature red accent (`#ff5c5c`), and layered frosted-glass surfaces that create depth without solid panels. Motion is crisp and purposeful — fast micro-interactions (100ms) with spring-loaded expansions. All interactive elements meet WCAG 2.1 AA contrast requirements on dark backgrounds.
|
||||
|
||||
## Contents
|
||||
|
||||
| File | What it covers |
|
||||
| ---------------------------------------- | --------------------------------------------------------------------------------------- |
|
||||
| [glass-surfaces.md](./glass-surfaces.md) | Two glass tiers, exact CSS values, no-solid-panels rule, `@supports` fallback |
|
||||
| [color-tokens.md](./color-tokens.md) | All design tokens with values, usage, contrast ratios, and anti-patterns |
|
||||
| [motion.md](./motion.md) | Duration scale, easing functions, `prefers-reduced-motion` pattern, animation inventory |
|
||||
| [accessibility.md](./accessibility.md) | WCAG checklist: contrast, focus, tap targets, ARIA, skip link, focus trap |
|
||||
|
||||
## Guiding Principles
|
||||
|
||||
1. **Glass, not solid** — Surfaces use `backdrop-filter` blur + semi-transparent backgrounds. No flat opaque panels in the main chrome.
|
||||
2. **Depth through layering** — The background scale (`--bg`, `--bg-accent`, `--bg-elevated`, `--bg-hover`, `--bg-muted`) communicates hierarchy without heavy borders.
|
||||
3. **Accent with restraint** — Signature red (`--accent: #ff5c5c`) for primary actions only; teal (`--accent-2`) for secondary/status.
|
||||
4. **Motion serves meaning** — Animations telegraph state changes; they never play for decoration alone.
|
||||
5. **Accessible by default** — Every component ships with focus-visible styles, correct ARIA roles, and ≥4.5:1 contrast on text.
|
||||
140
ui/docs/design-system/accessibility.md
Normal file
140
ui/docs/design-system/accessibility.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Accessibility
|
||||
|
||||
OpenClaw targets **WCAG 2.1 AA**. This checklist applies to every UI component — use it when building new components and during PR review.
|
||||
|
||||
---
|
||||
|
||||
## Contrast Minimums
|
||||
|
||||
| Context | Minimum Ratio | Notes |
|
||||
| ----------------------------------------- | ---------------- | -------------------------------------------------- |
|
||||
| Normal body text (< 18px / < 14px bold) | **4.5:1** | Use `--text` (#d4d4d8) or stronger on `--bg` |
|
||||
| Large text (≥ 18px regular / ≥ 14px bold) | **3:1** | Headings in chat thread |
|
||||
| UI component boundaries (inputs, buttons) | **3:1** | Border colours against adjacent background |
|
||||
| Focus indicators | **3:1** (AA) | `--focus-ring` / `--focus-glow` already compliant |
|
||||
| Placeholder text | Best-effort ≥3:1 | `--muted` (#838387) is ~5:1 on `--bg` — acceptable |
|
||||
|
||||
> Verify with [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/) or browser DevTools accessibility panel.
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
### 1. Focus Visible
|
||||
|
||||
- [ ] All interactive elements have a visible `:focus-visible` style
|
||||
- [ ] Use `--focus-ring` box-shadow pattern from `base.css` — do not remove the outline without replacing it
|
||||
- [ ] Never use `outline: none` alone; replace with `box-shadow: var(--focus-ring)` and `outline: none`
|
||||
|
||||
```css
|
||||
/* Correct pattern */
|
||||
.my-button:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Minimum Tap / Click Target Size
|
||||
|
||||
- [ ] All interactive controls are ≥ **44×44px** on mobile / touch targets
|
||||
- [ ] Icon-only buttons add invisible padding to reach 44px (`min-width: 44px; min-height: 44px`)
|
||||
- [ ] Verify on the mobile breakpoint (`@media (max-width: 768px)`)
|
||||
|
||||
### 3. Skip Link
|
||||
|
||||
- [ ] A visually-hidden skip link (`#skip-to-main`) is present in the document root
|
||||
- [ ] The link becomes visible on focus
|
||||
- [ ] Target element has `tabindex="-1"` to accept programmatic focus
|
||||
|
||||
```html
|
||||
<a id="skip-to-main" href="#main-content" class="skip-link">Skip to main content</a>
|
||||
```
|
||||
|
||||
```css
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
top: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
.skip-link:focus {
|
||||
left: 1rem;
|
||||
top: 1rem;
|
||||
/* ... visible styles */
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Focus Trap
|
||||
|
||||
- [ ] Modal dialogs trap focus inside while open (Tab/Shift+Tab cycle within the modal)
|
||||
- [ ] Closing the modal returns focus to the trigger element
|
||||
- [ ] Escape key closes the modal
|
||||
|
||||
### 5. ARIA Labels
|
||||
|
||||
- [ ] Icon-only buttons have `aria-label` describing the action (not the icon name)
|
||||
- [ ] Progress bars use `role="progressbar"` with `aria-valuenow`, `aria-valuemin`, `aria-valuemax`
|
||||
- [ ] Live regions updating async content use `aria-live="polite"` (or `"assertive"` for critical alerts)
|
||||
- [ ] Decorative SVG icons have `aria-hidden="true"`
|
||||
|
||||
```html
|
||||
<!-- Correct: icon button -->
|
||||
<button aria-label="Send message">
|
||||
<svg aria-hidden="true"><!-- ... --></svg>
|
||||
</button>
|
||||
|
||||
<!-- Correct: progress bar -->
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-valuenow="42"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-label="Context window usage"
|
||||
>
|
||||
<div style="width: 42%"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 6. Tab Role Pattern
|
||||
|
||||
When building a tab interface:
|
||||
|
||||
- [ ] Tab container: `role="tablist"`
|
||||
- [ ] Each tab: `role="tab"`, `aria-selected="true|false"`, `aria-controls="panel-id"`
|
||||
- [ ] Each panel: `role="tabpanel"`, `id` matching `aria-controls`, `aria-labelledby` pointing to its tab
|
||||
- [ ] Keyboard: Arrow keys move between tabs; Tab moves into the panel; Shift+Tab exits
|
||||
|
||||
```html
|
||||
<div role="tablist" aria-label="Main navigation">
|
||||
<button role="tab" aria-selected="true" aria-controls="chat-panel" id="chat-tab">Chat</button>
|
||||
<button role="tab" aria-selected="false" aria-controls="settings-panel" id="settings-tab">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
<div role="tabpanel" id="chat-panel" aria-labelledby="chat-tab"><!-- ... --></div>
|
||||
<div role="tabpanel" id="settings-panel" aria-labelledby="settings-tab" hidden><!-- ... --></div>
|
||||
```
|
||||
|
||||
### 7. Reduced Motion
|
||||
|
||||
- [ ] No animation plays without being suppressible via `prefers-reduced-motion`
|
||||
- [ ] The global reset in `base.css` covers transitions — test with "Reduce motion" enabled in OS settings
|
||||
- [ ] Infinite loaders (`shimmer`, spinners) have explicit `animation: none` in reduced-motion context
|
||||
|
||||
### 8. Semantic HTML
|
||||
|
||||
- [ ] Use native elements (`<button>`, `<a>`, `<input>`) before adding ARIA to `<div>` / `<span>`
|
||||
- [ ] Headings (`<h1>`–`<h6>`) reflect document hierarchy — do not skip levels
|
||||
- [ ] Lists of items use `<ul>` / `<ol>` + `<li>`, not chains of `<div>`
|
||||
|
||||
---
|
||||
|
||||
## Testing Tools
|
||||
|
||||
| Tool | Purpose |
|
||||
| ------------------------------------------------------- | ----------------------------------- |
|
||||
| Chrome DevTools → Accessibility tab | Inspect ARIA tree, contrast |
|
||||
| axe DevTools (browser extension) | Automated WCAG audit |
|
||||
| macOS VoiceOver (`Cmd+F5`) | Screen reader smoke test |
|
||||
| `prefers-reduced-motion: reduce` (DevTools → Rendering) | Verify animation suppression |
|
||||
| Keyboard-only navigation | Tab through entire UI without mouse |
|
||||
91
ui/docs/design-system/color-tokens.md
Normal file
91
ui/docs/design-system/color-tokens.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Color Tokens
|
||||
|
||||
All tokens are defined in `ui/src/styles/base.css` under `:root` (dark mode default) and `:root[data-theme-mode="light"]` (light override). Theme families may override accent tokens while keeping shared surface tokens.
|
||||
|
||||
> Contrast ratios are measured against `--bg` (`#0e1015`) in dark mode using WCAG relative luminance formula. AA requires ≥4.5:1 for normal text, ≥3:1 for large text and UI components.
|
||||
|
||||
---
|
||||
|
||||
## Background Scale
|
||||
|
||||
| Token | Dark Value | Light Value | Use | Don't |
|
||||
| --------------- | ---------- | ----------- | ----------------------------- | ------------------------------ |
|
||||
| `--bg` | `#0e1015` | `#f8f9fa` | Page root, deepest layer | Never use on elevated surfaces |
|
||||
| `--bg-accent` | `#13151b` | `#f1f3f5` | Sidebar, secondary panels | Not for interactive card hover |
|
||||
| `--bg-elevated` | `#191c24` | `#ffffff` | Raised panels, modals | Not for inline elements |
|
||||
| `--bg-hover` | `#1f2330` | `#eceef0` | List item hover state | Not for default state |
|
||||
| `--bg-muted` | `#1f2330` | `#eceef0` | Subtle fills, disabled states | Not for focus states |
|
||||
|
||||
## Surface / Card
|
||||
|
||||
| Token | Dark Value | Light Value | Use | Don't |
|
||||
| ---------------------- | ------------------------ | ------------------ | ----------------------------- | --------------- |
|
||||
| `--card` | `#161920` | `#ffffff` | Card backgrounds, composer | Avoid as border |
|
||||
| `--card-foreground` | `#f0f0f2` | `#1a1a1e` | Text on cards | — |
|
||||
| `--card-highlight` | `rgba(255,255,255,0.04)` | `rgba(0,0,0,0.02)` | Inner highlight on hover | Not for text |
|
||||
| `--popover` | `#191c24` | `#ffffff` | Dropdown, tooltip backgrounds | — |
|
||||
| `--popover-foreground` | `#f0f0f2` | `#1a1a1e` | Text inside popovers | — |
|
||||
|
||||
## Text
|
||||
|
||||
| Token | Dark Value | Contrast on `--bg` | Use |
|
||||
| ---------------- | ---------- | ------------------ | ---------------------------------------------------- |
|
||||
| `--text` | `#d4d4d8` | ~12.9:1 ✅ | Body copy, labels |
|
||||
| `--text-strong` | `#f4f4f5` | ~17.3:1 ✅ | Headings, emphasis |
|
||||
| `--muted` | `#838387` | ~5.0:1 ✅ | Placeholder, metadata |
|
||||
| `--muted-strong` | `#75757d` | ~4.2:1 | Secondary text, captions; avoid for normal body text |
|
||||
|
||||
## Accent (Primary — Red)
|
||||
|
||||
| Token | Value | Use | Don't |
|
||||
| ----------------- | --------------------- | ---------------------------------------------- | ---------------------------------------- |
|
||||
| `--accent` | `#ff5c5c` | Primary CTA, send button, active tab indicator | Don't use for large filled backgrounds |
|
||||
| `--accent-hover` | `#ff7070` | Hover state of accent elements | — |
|
||||
| `--accent-muted` | `#ff5c5c` | Same as accent (aliased) | — |
|
||||
| `--accent-subtle` | `rgba(255,92,92,0.1)` | Badge backgrounds, tinted fills | Not for text on dark bg (fails contrast) |
|
||||
| `--accent-glow` | `rgba(255,92,92,0.2)` | Focus rings, glow effects | Not as background |
|
||||
| `--primary` | `#ff5c5c` | Component library `primary` alias | — |
|
||||
|
||||
## Accent 2 (Teal)
|
||||
|
||||
| Token | Value | Use |
|
||||
| ------------------- | ---------------------- | ----------------------------------------- |
|
||||
| `--accent-2` | `#14b8a6` | Success-adjacent status, secondary badges |
|
||||
| `--accent-2-muted` | `rgba(20,184,166,0.7)` | Subtle teal fills |
|
||||
| `--accent-2-subtle` | `rgba(20,184,166,0.1)` | Tinted teal background |
|
||||
|
||||
## Semantic
|
||||
|
||||
| Token | Dark Value | Light Value | Contrast on `--bg` | Use |
|
||||
| --------------- | ---------- | ----------- | ------------------ | --------------------------------------------- |
|
||||
| `--ok` | `#22c55e` | `#15803d` | ~8.4:1 ✅ | Success states, token meter low |
|
||||
| `--warn` | `#f59e0b` | `#d97706` | ~8.9:1 ✅ | Warnings, degraded states |
|
||||
| `--danger` | `#ef4444` | `#dc2626` | ~5.1:1 ✅ | Errors, destructive actions, token meter high |
|
||||
| `--info` | `#3b82f6` | `#2563eb` | ~5.2:1 ✅ | Informational, token meter mid |
|
||||
| `--destructive` | `#ef4444` | — | ~5.1:1 ✅ | Destructive action labels |
|
||||
|
||||
## Border
|
||||
|
||||
| Token | Value | Use |
|
||||
| ----------------- | --------- | -------------------------------- |
|
||||
| `--border` | `#1e2028` | Default subtle borders, dividers |
|
||||
| `--border-strong` | `#2e3040` | Active/focused borders |
|
||||
| `--border-hover` | `#3e4050` | Hover-state borders |
|
||||
|
||||
## Focus
|
||||
|
||||
| Token | Value | Use |
|
||||
| -------------- | --------------------------------------------------------------------------------- | ------------------------------ |
|
||||
| `--ring` | `#ff5c5c` | Focus ring colour |
|
||||
| `--focus-ring` | `0 0 0 2px var(--bg), 0 0 0 3px color-mix(in srgb, var(--ring) 80%, transparent)` | Standard focus ring box-shadow |
|
||||
| `--focus-glow` | `0 0 0 2px var(--bg), 0 0 0 3px var(--ring), 0 0 16px var(--accent-glow)` | Elevated interactive elements |
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- ❌ Hardcoded hex colours in component CSS — always use tokens
|
||||
- ❌ `--accent-subtle` as text colour — fails contrast on dark backgrounds
|
||||
- ❌ Mixing `--ok` and `--accent-2` for "green success" — use `--ok` only
|
||||
- ❌ Using `--danger` for non-error states (e.g. "hot feature") — reserve for errors and destructive actions
|
||||
- ❌ `--muted-strong` for normal body text — below 4.5:1 on dark `--bg`; use `--text` instead
|
||||
94
ui/docs/design-system/glass-surfaces.md
Normal file
94
ui/docs/design-system/glass-surfaces.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Glass Surfaces
|
||||
|
||||
OpenClaw uses two glass tiers to establish visual hierarchy. **Never use flat, fully-opaque backgrounds** in the main chrome — glass creates the lived-in depth that defines the visual language.
|
||||
|
||||
---
|
||||
|
||||
## Tier 1 — Navigation Chrome
|
||||
|
||||
Used for: top nav, sidebar, persistent chrome overlays.
|
||||
|
||||
```css
|
||||
/* Exact values from ui/src/styles/layout.css — .topbar */
|
||||
background: color-mix(in srgb, var(--bg) 82%, transparent);
|
||||
backdrop-filter: blur(12px) saturate(1.6);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(1.6);
|
||||
```
|
||||
|
||||
**Why these values:** the topbar mixes the root background with transparency so it stays readable while still refracting content behind it. `saturate(1.6)` brings out colour from blurred layers.
|
||||
|
||||
---
|
||||
|
||||
## Tier 2 — Card / Input Surfaces
|
||||
|
||||
Used for: chat composer input box, modals, popovers, settings cards, dropdowns.
|
||||
|
||||
```css
|
||||
/* Exact values from ui/src/styles/chat/layout.css — .agent-chat__input */
|
||||
background: var(--card); /* #161920 */
|
||||
border: 1px solid var(--border); /* #1e2028 */
|
||||
border-radius: var(--radius-lg); /* 14px */
|
||||
|
||||
@supports (backdrop-filter: blur(1px)) {
|
||||
backdrop-filter: blur(12px) saturate(1.6);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(1.6);
|
||||
}
|
||||
```
|
||||
|
||||
Popovers and floating overlays reduce blur slightly:
|
||||
|
||||
```css
|
||||
/* Exact values from source */
|
||||
backdrop-filter: blur(
|
||||
8px
|
||||
); /* components.css action menu, cron-quick-create.css modal backdrop, workboard.css modal backdrop */
|
||||
backdrop-filter: blur(10px); /* skill-workshop.css revision dialog, dreams.css media lightbox */
|
||||
backdrop-filter: blur(14px); /* components.css markdown preview dialog backdrop */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The No-Solid-Panels Rule
|
||||
|
||||
> **Do not** add `background: var(--bg)` or any fully-opaque fill to a surface that appears above other content.
|
||||
|
||||
Use the `@supports` fallback pattern below instead. Browsers that lack `backdrop-filter` fall back gracefully to the semi-opaque base without breaking layout.
|
||||
|
||||
### `@supports` Fallback Pattern
|
||||
|
||||
```css
|
||||
.my-surface {
|
||||
/* Fallback for older browsers / Firefox without backdrop-filter */
|
||||
background: rgba(14, 16, 21, 0.92);
|
||||
}
|
||||
|
||||
@supports (backdrop-filter: blur(1px)) {
|
||||
.my-surface {
|
||||
background: rgba(14, 16, 21, 0.75);
|
||||
backdrop-filter: blur(12px) saturate(1.6);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(1.6);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When to Use Each Tier
|
||||
|
||||
| Context | Tier | Blur |
|
||||
| ---------------------- | ---- | ---- |
|
||||
| Top nav / sidebar rail | 1 | 12px |
|
||||
| Chat composer | 2 | 12px |
|
||||
| Modal / dialog | 2 | 10px |
|
||||
| Dropdown / popover | 2 | 8px |
|
||||
| Tooltip | 2 | 14px |
|
||||
| Workboard card | 2 | 10px |
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- ❌ `background: var(--bg)` on a surface element (use glass instead)
|
||||
- ❌ `backdrop-filter` without `-webkit-backdrop-filter` (Safari still requires prefix)
|
||||
- ❌ Nesting glass surfaces more than 2 levels (causes noticeable blur stacking on macOS)
|
||||
- ❌ Using `blur(>16px)` outside of modal overlays (hurts performance on mid-tier devices)
|
||||
91
ui/docs/design-system/motion.md
Normal file
91
ui/docs/design-system/motion.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Motion
|
||||
|
||||
OpenClaw uses a three-step duration scale with purpose-matched easing functions. Every animation should serve a functional goal — transitions that exist only for aesthetics add cognitive load without benefit.
|
||||
|
||||
---
|
||||
|
||||
## Duration Scale
|
||||
|
||||
Defined in `ui/src/styles/base.css`:
|
||||
|
||||
| Token | Value | Use |
|
||||
| ------------------- | ------- | --------------------------------------------------------- |
|
||||
| `--duration-fast` | `100ms` | Micro-interactions: hover colour, focus ring, icon swap |
|
||||
| `--duration-normal` | `180ms` | Standard transitions: menu open, tab switch, input expand |
|
||||
| `--duration-slow` | `300ms` | Page-level: sheet slide-in, modal fade, skeleton reveal |
|
||||
|
||||
Non-token durations in use (document when adding new ones):
|
||||
|
||||
| Context | Value | File |
|
||||
| ----------------------- | ------------------------------ | ----------------- |
|
||||
| Theme circle transition | `400ms` | `base.css` |
|
||||
| Shimmer animation | `1500ms` | `base.css` |
|
||||
| Composer border/shadow | `var(--duration-fast)` = 100ms | `chat/layout.css` |
|
||||
|
||||
---
|
||||
|
||||
## Easing Functions
|
||||
|
||||
Defined in `ui/src/styles/base.css`:
|
||||
|
||||
| Token | Curve | Use |
|
||||
| --------------- | ----------------------------------- | ------------------------------------------------------- |
|
||||
| `--ease-out` | `cubic-bezier(0.16, 1, 0.3, 1)` | Most enter/expand transitions — fast start, smooth land |
|
||||
| `--ease-in-out` | `cubic-bezier(0.4, 0, 0.2, 1)` | Elements that travel across screen (slides, drawers) |
|
||||
| `--ease-spring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` | Playful/tactile: button press, badge pop, icon bounce |
|
||||
|
||||
**Default rule of thumb:** Use `--ease-out` unless the element explicitly moves from point A to point B (use `--ease-in-out`) or needs a bouncy feel (use `--ease-spring`).
|
||||
|
||||
---
|
||||
|
||||
## `prefers-reduced-motion` Pattern
|
||||
|
||||
Every animation or transition **must** be suppressed when the user has requested reduced motion. Use the global reset already present in `base.css` — do not add per-component overrides unless you need to preserve a non-animated state change (e.g. instant opacity change is acceptable, instant position snap is acceptable).
|
||||
|
||||
```css
|
||||
/* Already in base.css — covers all transitions globally */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For components with complex animation state (e.g. shimmer skeletons that use `animation-iteration-count: infinite`), add an explicit guard:
|
||||
|
||||
```css
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.my-shimmer {
|
||||
animation: none;
|
||||
/* Show a static placeholder instead */
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Animation Inventory
|
||||
|
||||
| Name | File | Duration | Purpose |
|
||||
| ------------------------- | ----------------- | ------------------------- | ----------------------------------------------- |
|
||||
| `shimmer` | `base.css` | 1500ms, infinite | Skeleton loading placeholders |
|
||||
| `theme-circle-transition` | `base.css` | 400ms, `--ease-out` | Dark/light mode circle wipe |
|
||||
| Composer border/shadow | `chat/layout.css` | 100ms (`--duration-fast`) | Focus ring on input area |
|
||||
| Workboard card glass | `workboard.css` | — | Static (no animation) |
|
||||
| Dreams diary reveal | `dreams.css` | 1.4s, cubic-bezier | Entry reveal keyframe with blur-to-clear effect |
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- ❌ Adding new keyframe animations without a `prefers-reduced-motion` suppression
|
||||
- ❌ Using `animation-iteration-count: infinite` outside of skeleton loaders
|
||||
- ❌ Duration > 500ms for UI chrome elements (feels sluggish)
|
||||
- ❌ `linear` easing for enter/exit — always use a curve from the token set
|
||||
- ❌ CSS transitions on `transform` and `opacity` simultaneously with `filter` — causes GPU layer explosion on mobile
|
||||
Reference in New Issue
Block a user