feat: add antfu-design skill (#32)

This commit is contained in:
Anthony Bot
2026-06-23 09:05:55 +09:00
committed by GitHub
parent 50deaeb269
commit 11cefdc7fd
17 changed files with 1362 additions and 0 deletions
+1
View File
@@ -33,6 +33,7 @@ Manually maintained by Anthony Fu with his preferred tools, setup conventions, a
| Skill | Description |
|-------|-------------|
| [antfu](skills/antfu) | Anthony Fu's preferences and best practices for app/library projects (eslint, pnpm, vitest, vue, etc.) |
| [antfu-design](skills/antfu-design) | UnoCSS-centered design principles, semantic tokens, and UI presentation patterns from Anthony Fu's tooling UIs |
### Skills Generated from Official Documentation
+1
View File
@@ -72,4 +72,5 @@ export const vendors: Record<string, VendorSkillMeta> = {
*/
export const manual = [
'antfu',
'antfu-design',
]
+27
View File
@@ -0,0 +1,27 @@
# antfu design
The antfu-style UnoCSS design skill: class-first semantic tokens with dual light/dark
for tooling and devtools UIs, plus design-read, anti-slop, and micro-interaction polish
for broader product and marketing surfaces.
This README is for humans and is not part of the skill loaded by agents (that is
`SKILL.md` plus `references/`).
## Credits
Some patterns were adapted into antfu's UnoCSS, class-first, framework-agnostic idiom
from two third-party Agent Skills:
- **taste-skill** (`design-taste-frontend`) by Leonxlnx
https://github.com/Leonxlnx/taste-skill
Anti-slop discipline, the design read and three dials, design-engineering bias
correction, the pattern vocabulary, and the redesign protocol.
- **make-interfaces-feel-better** by Jakub Krehel
https://github.com/jakubkrehel/make-interfaces-feel-better
Micro-interaction polish: concentric radius, optical alignment, shadows, staggered
enters, tabular numbers, image outlines, tap scale, and hit areas.
These upstream projects are maintained by their own authors under their own licenses.
Before redistributing, check each repository's license and attribution terms. This
README and the in-file `Source references` footers exist to credit that upstream work.
+93
View File
@@ -0,0 +1,93 @@
---
name: antfu-design
description: antfu-style design conventions, broadened. UnoCSS-first, class-based semantic tokens with dual light/dark for tooling and devtools UIs, plus design-read, anti-slop, and micro-interaction polish for landing pages and product surfaces. Use when building or refactoring any interface with UnoCSS.
metadata:
author: Anthony Fu
version: "2026.06.22"
---
Use this when building interfaces with UnoCSS in any framework (React, Vue, Svelte, Solid, or plain HTML), from dense devtools panels to landing pages. Read core-design-read first to set the direction, then apply the token system plus the polish and anti-slop rules.
## Core Rules
- Use semantic shortcuts (`bg-base`, `border-base`, `color-active`, `btn-action`) instead of raw utility chains in markup.
- Design light and dark mode together. Core tokens must work in both themes.
- Name z-index layers (`z-top-nav`, `z-panel-content`, `z-drawer-content`). Do not use raw z values in templates.
- Generate class-based utilities only (`class="..."`). Avoid Attributify syntax in generated code.
- Keep icon/status class strings literal so UnoCSS can statically extract them (`// @unocss-include` when needed).
- Use `font-mono` + `tabular-nums` for technical values (paths, SHAs, counters, timestamps, percentages).
- For long paths and IDs, truncate visually but keep the full value in `title`.
- Read the brief and set the three dials before choosing a look (core-design-read).
- Borders for dense or structural surfaces, layered shadows for elevated ones (features-micro-interactions).
- Zero em-dash and en-dash characters in any user-facing text (best-practices-anti-slop).
## Starter shortcuts
A minimal semantic core. See core-starter-kit for the full `uno.config.ts` and base styles.
```ts
shortcuts: [
{
'color-base': 'color-neutral-800 dark:color-neutral-200',
'bg-base': 'bg-white dark:bg-#111',
'bg-secondary': 'bg-#eee dark:bg-#222',
'border-base': 'border-#8882',
'bg-active': 'bg-#8881',
'color-active': 'color-primary-600 dark:color-primary-300',
'border-active': 'border-primary-600/25 dark:border-primary-400/25',
'btn-action': 'inline-flex items-center gap-2 rounded border border-base px2 py1 op75 hover:op100 hover:bg-active disabled:pointer-events-none disabled:op30!',
'op-fade': 'op65 dark:op55',
'op-mute': 'op30 dark:op25',
'z-top-nav': 'z-60',
'z-panel-content': 'z-70',
'z-drawer-content': 'z-100',
},
]
```
## Core References
| Topic | Description | Reference |
|-------|-------------|-----------|
| Core Principles | Semantic tokens, dark mode parity, z-index naming, class-first output | [core-principles](references/core-principles.md) |
| Starter Kit | Copy-paste UnoCSS starter config and base light/dark styles | [core-starter-kit](references/core-starter-kit.md) |
| Tokens and Combos | Token families, reusable class combinations, mobile-safe shell tokens | [core-tokens-and-combinations](references/core-tokens-and-combinations.md) |
| Design Read and Dials | Read the brief, declare a design read, set variance/motion/density dials | [core-design-read](references/core-design-read.md) |
## Best Practices
| Topic | Description | Reference |
|-------|-------------|-----------|
| Strict Rules and Pre-Flight | Do/don't checklist and the consolidated pre-flight | [best-practices-strict-rules](references/best-practices-strict-rules.md) |
| Class over Attributify | Why generated code uses class utilities, with conversions | [best-practices-class-utilities-over-attributify](references/best-practices-class-utilities-over-attributify.md) |
| Anti-Slop Hygiene | The dash ban and the AI-tell forbidden patterns | [best-practices-anti-slop](references/best-practices-anti-slop.md) |
| Bias Correction | Typography, color, layout, and materiality defaults to override | [best-practices-bias-correction](references/best-practices-bias-correction.md) |
## Features
| Topic | Description | Reference |
|-------|-------------|-----------|
| Data Presentation | Paths, icons, time, date, numbers, badges, buttons | [features-data-presentation](references/features-data-presentation.md) |
| Micro-Interactions | Radius, alignment, shadows, animation, numbers, outlines, hit areas | [features-micro-interactions](references/features-micro-interactions.md) |
| Floating Vue Overrides | Shared Floating Vue setup and popper styling | [features-floating-vue-overrides](references/features-floating-vue-overrides.md) |
## Advanced
| Topic | Description | Reference |
|-------|-------------|-----------|
| Pattern Vocabulary | Named UI patterns to recognize and reach for | [advanced-pattern-vocabulary](references/advanced-pattern-vocabulary.md) |
| Redesign Protocol | Detect mode, audit first, preserve IA and SEO | [advanced-redesign-protocol](references/advanced-redesign-protocol.md) |
<!--
Source references:
- https://github.com/antfu/node-modules-inspector
- https://github.com/vitejs/devtools/tree/main/packages/rolldown
- https://github.com/eslint/config-inspector
- https://github.com/antfu/vite-plugin-inspect
- https://github.com/antfu/agent-container
- https://github.com/Leonxlnx/taste-skill
- https://github.com/jakubkrehel/make-interfaces-feel-better
-->
@@ -0,0 +1,43 @@
---
name: advanced-pattern-vocabulary
description: A vocabulary of named UI patterns the agent should recognize and reach for when the design read calls for them. Names only, not implementations.
---
# Pattern Vocabulary
Know these names so you can reason about and reach for the right pattern. This is a vocabulary, not a component library. Reach for any of them only when the design read and dials justify it; most tooling UIs need almost none.
## Hero
- **Asymmetric split**: message on one side, asset on the other.
- **Editorial manifesto**: large type, no asset.
- **Media-mask**: type cut as a mask over video.
- **Scroll-pinned**: hero pins while content scrolls behind.
## Navigation and menus
- **Dock magnification**, **magnetic button**, **dynamic island** (morphing status pill), **mega-menu reveal**.
## Layout and grids
- **Bento grid** (asymmetric tiles), **masonry**, **split-screen scroll**, **sticky-stack sections**.
## Cards
- **Parallax tilt**, **spotlight border**, **glassmorphism panel**, **morphing modal** (button expands into its dialog).
## Scroll
- **Sticky scroll stack**, **horizontal scroll hijack**, **zoom parallax**, **scroll progress path**.
## Media
- **Coverflow carousel**, **drag-to-pan grid**, **hover image trail**, **accordion image slider**.
## Typography
- **Kinetic marquee**, **text-mask reveal**, **text scramble**, **gradient stroke**.
## Micro-interactions
- **Directional hover-aware button** (fill enters from the cursor side), **ripple click**, **skeleton shimmer**, **mesh-gradient background**, **lens-blur depth**.
## Animation library choice
- Default to a small motion library for UI state changes. Use a scroll library only for genuine scroll choreography, isolated in a dedicated leaf with cleanup. Do not mix two animation engines in one component tree; they fight over frames.
<!--
Source references:
- https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md
-->
@@ -0,0 +1,42 @@
---
name: advanced-redesign-protocol
description: Handle redesigns without breaking what works. Detect the mode, audit first, preserve IA and SEO, and apply modernisation levers in order.
---
# Redesign Protocol
Misclassifying a redesign is the biggest source of bad redesign output.
## Detect the mode
- **Greenfield**: no existing site, or a full overhaul is approved. Use the dial baselines in core-design-read.
- **Redesign, preserve**: modernise without breaking the brand. Audit first, extract brand tokens, evolve.
- **Redesign, overhaul**: new visual language over existing content. Treat visuals as greenfield, but keep content and information architecture.
If ambiguous, ask once: should this preserve the existing brand, or start visually from scratch?
## Audit before touching
Document the current state first: brand tokens (colors, type, logo, radii), information architecture and nav, content blocks that do real work vs filler, signature patterns to keep, slop and broken layouts to retire, the current dial reading, and the SEO baseline. SEO migration is the top redesign risk.
## Modernisation levers (in order)
Apply in order, stop when the brief is satisfied:
1. Typography refresh (biggest lift per unit of risk).
2. Spacing and vertical rhythm.
3. Color recalibration (desaturate, unify neutrals, keep the brand accent).
4. Motion layer appropriate to the MOTION dial.
5. Hero and key-section recomposition.
6. Full block replacement (only when a block is unsalvageable).
Rule of thumb: if IA, content, and SEO are sound, prefer targeted evolution (levers 1 to 4): about 70% of the value at 40% of the risk.
## Never change silently
Do not modify these without explicit approval: URL structure and route slugs, primary nav labels, form field names or order (breaks analytics and autofill), brand logo or wordmark, and legal, consent, or cookie copy.
<!--
Source references:
- https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md
-->
@@ -0,0 +1,49 @@
---
name: best-practices-anti-slop
description: Avoid the signatures that make generated interfaces look templated. Hard bans on em-dashes, generic names, fake-perfect numbers, filler verbs, fake screenshots, and decorative clutter.
---
# Anti-Slop Hygiene
These are the patterns LLMs reach for when trying to "look designed". Treat them as bans unless the brief explicitly asks for one.
## Dash ban (non-negotiable)
The em-dash character (Unicode U+2014) and the en-dash (U+2013) are the single biggest text tell. Do not emit either character anywhere visible: headlines, labels, body copy, quotes, attribution, captions, button text, alt text. The only permitted dash is the regular hyphen (`-`).
To replace a dash, restructure: use a period, a comma, parentheses, a colon, a line break, or a column. Ranges (`2018-2026`, `40-80k`) use a hyphen.
## Visual and CSS tells
- No neon or outer glows by default. Use inner borders or subtle tinted shadows.
- No pure black (`#000`) or pure white (`#fff`). Use off-black (`#111`, neutral-950) and off-white.
- No AI-purple or violet gradient as the reflex accent. Pick a neutral base plus one intentional accent.
- No oversaturated accents, no excessive gradient text on large headings.
- No custom mouse cursors.
## Content and data tells (the "Jane Doe" effect)
- No generic names ("John Doe", "Jane Doe"). Use realistic, locale-appropriate names.
- No fake-perfect numbers (`99.99%`, `50%`, `1234567`). Use organic values, or label mock data clearly. Do not fake engineering precision a product does not claim.
- No startup-slop brand names ("Acme", "Nexus", "SmartFlow"). Invent names that sound real and contextual.
- No filler verbs ("Elevate", "Seamless", "Unleash", "Next-Gen", "Revolutionize"). Use concrete verbs.
- No generic avatars (SVG "egg", default user glyphs). Use believable placeholders.
## Decoration tells
- No fake product UI built from `<div>` rectangles (fake terminals, dashboards, task lists). Use a real screenshot, a generated image, a real component preview, or nothing.
- No decorative status dots before every nav item, list row, or badge. A colored dot is allowed only for real semantic state, used sparingly.
- No scroll cues ("Scroll", "scroll to explore", animated wheels). The user already knows what scrolling is.
- No section-number eyebrows (`00 / INDEX`, `001 Capabilities`). Name the topic in plain language, or drop the label.
- No version stamps on marketing pages (`v1.4.2`, `Build 0048`, `last sync 4s ago`). These are devtool fixtures, not landing-page content.
- No locale, time, or weather strips ("Lisbon 14:23, 18C") unless the brief is genuinely place or timezone focused.
- Ration the middle dot (`·`): at most one per metadata line, never as the universal separator.
## Copy self-audit
Before shipping, re-read every visible string. Flag anything grammatically broken, with unclear referents, or that reads like the model trying to sound thoughtful (forced wordplay, mock-humble craftsman labels). Replace flagged strings with plain functional sentences. Boring but correct beats cute but wrong.
<!--
Source references:
- https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md
-->
@@ -0,0 +1,44 @@
---
name: best-practices-bias-correction
description: Counter the default cliches in typography, color, layout, and materiality. One accent color, intentional type, diversified layouts, and restrained elevation.
---
# Bias Correction
LLMs default to cliches. Override them deliberately based on the design read.
## Typography
- **Headlines**: control hierarchy with weight and color, not raw scale. Avoid oversized H1s that only shout.
- **Body**: keep measure readable (`max-w-[65ch]`), relaxed leading, muted color (`op-fade`).
- **Sans choice**: avoid Inter as a reflex default. The antfu default of `DM Sans` (with `DM Mono` for technical values) already satisfies this; pick a brand-appropriate face otherwise.
- **Serif discipline**: serif is not the default for "creative" or "premium". Use serif only when the brand names one, or the aesthetic is genuinely editorial, luxury, or publication and you can say why. Do not reach for Fraunces or Instrument Serif as the default display serif.
- **Emphasis**: emphasize a word with italic or bold of the same family. Do not splice a serif word into a sans headline.
- **Mono**: reserve `font-mono` plus `tabular-nums` for technical metadata (paths, SHAs, counters, timestamps, percentages), not body text.
## Color
- One accent color per project. Keep saturation moderate (under roughly 80%) unless the brand is loud.
- Neutral base (`bg-base`, `color-base`, `border-base`) first, then one intentional accent (`color-active`). The antfu green primary is one such accent; swap it, do not multiply it.
- No AI-purple glow as the reflex accent. If the brand is purple, embrace it with intent.
- **Color consistency lock**: once an accent is chosen, use it across the whole page. A warm-grey page does not grow a blue CTA in section 7.
- Keep one neutral temperature (warm or cool grey), not both.
## Layout diversification
- **Anti-center bias**: when `VARIANCE > 4`, avoid the centered-hero-over-dark-gradient default. Reach for split, left-aligned-with-asset, or asymmetric whitespace.
- Centered layouts are fine for editorial or manifesto messages where the words are the design.
- Vary section layout families: do not repeat the same three-equal-cards or left-image/right-text block down the whole page.
- Use CSS Grid for column layouts, not flexbox percentage math (`grid grid-cols-1 md:grid-cols-3 gap-6`).
## Materiality and elevation
- Use cards only when elevation communicates real hierarchy. Otherwise group with `border-t`, dividers, or whitespace. (See features-micro-interactions for borders vs shadows by context.)
- Tint shadows toward the background hue. No pure-black drop shadows on light surfaces.
- **Shape consistency lock**: pick one corner-radius scale and keep it. If buttons are pill and cards are `rounded-lg`, document the rule and follow it everywhere.
- At high density, drop card containers entirely: separate data with hairlines and spacing.
<!--
Source references:
- https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md
-->
@@ -0,0 +1,84 @@
---
name: best-practices-class-utilities-over-attributify
description: Prefer normal class utilities over Attributify syntax for generated UnoCSS code; includes conversion patterns.
---
# Class Utilities Over Attributify
## Recommendation
- Generate normal class strings by default.
- Keep `presetAttributify()` only for compatibility with existing code, not as the primary authoring format.
- In shared examples and templates, always normalize to class-based utilities.
## Why This Works Better for Agents
- One attribute (`class`) is easier to compose, diff, and refactor than many utility-like attributes.
- Less ambiguity around merge behavior when multiple tools touch markup.
- More portable across JSX, template strings, markdown snippets, and framework-agnostic examples.
## Normalized Class Patterns
Use these as default templates when generating code:
```html
<div class="flex items-center gap-2 px2 py1 border border-base rounded">
Content
</div>
```
```html
<button class="op75 hover:op100 hover:bg-active disabled:pointer-events-none disabled:op30!">
Save
</button>
```
```html
<div class="border-l border-base pl2">Meta</div>
```
```html
<button class="flex gap-2 items-center border border-base rounded px2 py1 hover:bg-active op75">
<span class="i-ph-arrow-clockwise-duotone"></span>
refresh
</button>
```
## Config Pattern
If you want to discourage Attributify in new code:
```ts
import { defineConfig, presetIcons, presetWind4 } from 'unocss'
export default defineConfig({
presets: [
presetWind4(),
presetIcons(),
// Do not include presetAttributify() for new projects.
],
})
```
If you need migration compatibility:
```ts
import { defineConfig, presetAttributify, presetWind4 } from 'unocss'
export default defineConfig({
presets: [
presetWind4(),
presetAttributify(),
],
})
```
But still write new components using `class="..."`.
<!--
Source references:
- https://github.com/antfu/node-modules-inspector/blob/main/packages/node-modules-inspector/src/uno.config.ts
- https://github.com/vitejs/devtools/blob/main/packages/ui/src/unocss/index.ts
- https://github.com/eslint/config-inspector/blob/main/uno.config.ts
- https://github.com/antfu/vite-plugin-inspect/blob/main/uno.config.ts
-->
@@ -0,0 +1,102 @@
---
name: best-practices-strict-rules
description: High-signal do/don't checklist for stable, class-based UnoCSS UI generation.
---
# Strict Rules
Use this as a hard checklist before returning UI code.
## Must Do
- Declare a design read and set the three dials from an appropriate baseline before choosing a look (core-design-read).
- Define semantic shortcuts first (`bg-base`, `border-base`, `color-base`, `color-active`, `btn-action`).
- Ensure core tokens work in both light and dark mode.
- Name depth layers (`z-top-nav`, `z-panel-content`, `z-drawer-content`) and use those names in markup.
- Generate class-based utilities only (`class="..."`).
- Keep icon/status class strings literal in code paths that may be tree-shaken (`// @unocss-include` when needed).
- Use `font-mono` + `tabular-nums` for technical values (counts, SHAs, timestamps, percentages).
- For truncated path/ID labels, keep full value in `title`.
- Use one accent color, with neutral primitives first.
- Use concentric radius on nested rounded elements; honor `prefers-reduced-motion` for any non-trivial motion.
## Must Not Do
- Do not use raw z-index numbers directly in component markup.
- Do not scatter raw color values in templates when a semantic token exists.
- Do not emit Attributify-style utility attributes in generated examples.
- Do not build icon class names via interpolation that UnoCSS cannot statically detect.
- Do not design only for one theme; avoid light-only or dark-only primitives.
- Do not apply modifiers to semantic shortcuts (`border-base/60` is invalid); define a new token for a new variation.
- Do not emit the em-dash (U+2014) or en-dash (U+2013) in any user-facing text (best-practices-anti-slop).
- Do not ship AI-slop tells: generic names, fake-perfect numbers, AI-purple defaults, or div-based fake screenshots.
- Do not use `transition: all`; specify exact properties.
## Return-Ready Snippet Pattern
```html
<div class="w-screen h-screen flex flex-col of-hidden bg-base color-base font-sans">
<header class="h-nav shrink-0 flex items-center gap-2 px-3 border-b border-base bg-base z-top-nav">
<button class="btn-action-icon" aria-label="Open menu">
<span class="i-ph-list-duotone"></span>
</button>
<span class="font-mono text-xs truncate" title="workspace-name">workspace-name</span>
</header>
<main class="flex-1 min-h-0 of-auto">
<div class="p-4 flex items-center gap-2">
<span class="inline-flex items-center gap-1 px-1.5 py-px rounded border border-active bg-active color-active text-micro uppercase tracking-wide">
active
</span>
<span class="text-micro font-mono tabular-nums op-fade">12,480</span>
</div>
</main>
</div>
```
## Consolidated Pre-Flight Checklist
Run before returning UI code. If a box cannot be ticked honestly, it is not done.
Foundations
- design read declared, dials set from an appropriate baseline (core-design-read)
- semantic shortcuts used in markup
- light/dark-safe base tokens present
- named z-layer shortcuts used (no raw z-index in markup)
- class-only utilities (no Attributify)
Data and type
- long path/ID labels truncate with full value in `title`
- technical values use mono + tabular-nums
- one accent color, neutral base, no AI-purple default
- serif only when justified; not Inter by reflex
Polish
- nested rounded elements use concentric radius
- borders for dense or structural surfaces, layered shadows for elevated ones
- enters split and staggered, exits subtle, no `transition: all`
- dynamic numbers use tabular-nums, images have a pure black or white outline
- interactive controls have a 40x40px hit area
- motion above the static band honors `prefers-reduced-motion`
Anti-slop
- zero em-dash and en-dash characters anywhere visible
- no generic names, fake-perfect numbers, filler verbs, or div-based fake screenshots
- no decorative dots, scroll cues, or version stamps unless the brief calls for them
- every visible string re-read in a copy self-audit
Redesign (if applicable)
- mode detected, audit done, IA, SEO, and nav labels preserved
<!--
Source references:
- https://github.com/antfu/node-modules-inspector/blob/main/packages/node-modules-inspector/src/uno.config.ts
- https://github.com/vitejs/devtools/blob/main/packages/ui/src/unocss/shared-shortcuts.ts
- https://github.com/vitejs/devtools/blob/main/packages/ui/src/unocss/shortcuts.ts
- https://github.com/eslint/config-inspector/blob/main/uno.config.ts
- https://github.com/antfu/vite-plugin-inspect/blob/main/uno.config.ts
- https://github.com/antfu/agent-container/blob/main/hub/uno.config.ts
- https://github.com/antfu/agent-container/blob/main/hub/app/state/status.ts
- https://github.com/Leonxlnx/taste-skill
- https://github.com/jakubkrehel/make-interfaces-feel-better
-->
@@ -0,0 +1,64 @@
---
name: core-design-read
description: Read the brief before building. Infer page kind, audience, and vibe, declare a one-line design read, then set three dials (variance, motion, density) using the Tooling baseline or the Marketing baseline.
---
# Design Read and Dials
Before writing any markup, infer what the interface actually needs. Most weak output comes from jumping to a default look instead of reading the brief.
## 1. Read the Brief
Infer these signals first:
- **Kind**: tooling/devtools panel, dashboard, app shell, landing page, portfolio, docs, or marketing section.
- **Audience**: maintainers and power users (dense, keyboard-first) vs. first-time visitors (guided, spacious). The audience picks the aesthetic, not your taste.
- **Vibe words** the user used: "minimal", "Linear-style", "playful", "premium", "brutalist", "editorial", "dark tech".
- **References**: linked URLs, screenshots, named products or competitors.
- **Existing brand assets**: logo, colors, type, photography. For redesigns these are starting material, not optional input (see advanced-redesign-protocol).
- **Hard constraints**: accessibility-first, regulated, public-sector, trust-first. These override aesthetic preference.
## 2. Declare the Design Read
Before generating, state one line:
> Reading this as: `<kind>` for `<audience>`, with a `<vibe>` language, leaning toward `<token system or aesthetic family>`.
Examples:
- Reading this as: devtools inspector panel for maintainers, with a dense flat language, leaning toward the antfu UnoCSS token system.
- Reading this as: SaaS landing for technical buyers, with a calm minimal language, leaning toward UnoCSS utilities plus restrained motion.
If the brief is genuinely ambiguous, ask exactly one clarifying question. If you can infer confidently, do not ask. Just declare the read and proceed.
## 3. Set Three Dials
Tune every layout, motion, and density choice against three dials (1 to 10):
- **VARIANCE**: 1 = perfectly symmetric, 10 = asymmetric and artsy.
- **MOTION**: 1 = static, 10 = cinematic and physics-driven.
- **DENSITY**: 1 = airy gallery, 10 = packed cockpit.
### Two baselines
| Baseline | VARIANCE | MOTION | DENSITY |
|----------|----------|--------|---------|
| Tooling | 2 to 3 | 2 to 3 | 7 to 9 |
| Marketing | 7 to 8 | 5 to 7 | 3 to 4 |
- **Tooling** (the antfu default): devtools, dashboards, app shells. Information-dense and quiet: tight spacing, mono numbers, minimal motion.
- **Marketing**: landing pages, portfolios, product pages. They breathe and move.
Override either baseline only when the design read calls for it.
### What the dials drive
- **VARIANCE** 1 to 3: symmetric grids, equal padding. 4 to 7: offset headers, varied aspect ratios. 8 to 10: asymmetric grids, large empty zones. Above `md`, high-variance layouts must collapse to a single column on small screens.
- **MOTION** 1 to 3: hover and active states only. 4 to 7: fluid CSS transitions on transform and opacity. 8 to 10: scroll-driven reveals (see features-micro-interactions for the rules). Any motion above 3 must honor `prefers-reduced-motion`.
- **DENSITY** 1 to 3: large section gaps. 4 to 7: standard app spacing. 8 to 10: tight padding, hairline separators, mono for all numbers.
<!--
Source references:
- https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md
- https://github.com/antfu/node-modules-inspector
-->
@@ -0,0 +1,153 @@
---
name: core-principles
description: Framework-agnostic UnoCSS design principles with semantic shortcuts, dark mode parity, named depth layers, and class-based utility generation.
---
# Core Principles
## Semantic First
- Define visual meaning once in `shortcuts` and reuse it everywhere.
- Keep app code semantic (`bg-base`, `border-base`, `btn-action`) instead of raw color values.
- Treat tokens as API surface: rename rarely, extend intentionally.
- Shortcuts are names, not utilities: you cannot apply modifiers to them. `border-base/60` is invalid, since a shortcut does not accept an opacity suffix. When you need a new variation, define a new token (for example `border-base-strong`) instead of modifying an existing one.
## Extracted Design Philosophy
- Build neutral foundations first (`bg-base`, `color-base`, `border-base`), then layer accents.
- Keep information density high but readable: compact spacing, micro text tiers, and clear opacity hierarchy.
- Use `font-mono` selectively for technical metadata (paths, SHAs, counters, timestamps), not body text.
- Prefer icon + label pairs over icon-only controls unless the action is universally obvious.
- Use deterministic color mapping for unbounded labels (cluster names, plugin names, tags) to preserve recognition.
- Self-host fonts (or load locally at build time) when possible; avoid runtime font CDN dependency.
## Light and Dark Together
- Every core token must define both light and dark behavior.
- Build neutral primitives (`bg-base`, `bg-secondary`, `border-base`, `color-base`) before feature colors.
- Add accent tokens (`color-active`, `border-active`, status colors) on top of neutral primitives.
## Name Depth Layers
- Never drop raw z-index values in templates.
- Define layer names in `shortcuts` (`z-top-nav`, `z-panel-content`, `z-drawer-content`).
- Keep one place where stack order is reasoned about.
## Framework-Agnostic Class Composition
- Store shared class combinations as string constants (or utility functions) and reuse them across frameworks.
- Keep the same class token language in React, Vue, Svelte, and plain HTML.
```ts
export const ui = {
panel: 'bg-base color-base border border-base rounded-lg',
button: 'btn-action-sm',
dimText: 'op-fade text-mini',
}
```
```tsx
// React
export function Toolbar() {
return (
<div className={`h-10 flex items-center gap-2 px-3 border-b border-base ${ui.panel}`}>
<button className={ui.button}>Refresh</button>
</div>
)
}
```
```svelte
<!-- Svelte -->
<div class={`h-10 flex items-center gap-2 px-3 border-b border-base ${ui.panel}`}>
<button class={ui.button}>Refresh</button>
</div>
```
## Class-Based Output, Not Attributify
- Prefer normal utility classes for generated code.
- Avoid Attributify-style attributes for agent output because class strings are easier to generate and refactor safely.
- If a legacy codebase already uses Attributify, keep compatibility, but still generate new code with `class="..."`.
```html
<button class="btn-action text-sm">Refresh</button>
```
## Baseline UnoCSS Config
```ts
import { createLocalFontProcessor } from '@unocss/preset-web-fonts/local'
import {
defineConfig,
presetIcons,
presetWebFonts,
presetWind4,
transformerDirectives,
transformerVariantGroup,
} from 'unocss'
export default defineConfig({
theme: {
colors: {
primary: {
300: '#7CBC71',
400: '#49833E',
600: '#396831',
DEFAULT: '#49833E',
},
},
fontSize: {
micro: ['0.625rem', '0.875rem'],
mini: ['0.6875rem', '1rem'],
compact: ['0.8125rem', '1.125rem'],
},
},
shortcuts: [
{
'color-base': 'color-neutral-800 dark:color-neutral-200',
'bg-base': 'bg-white dark:bg-#111',
'bg-secondary': 'bg-#eee dark:bg-#222',
'border-base': 'border-#8882',
'bg-active': 'bg-#8881',
'color-active': 'color-primary-600 dark:color-primary-300',
'border-active': 'border-primary-600/25 dark:border-primary-400/25',
'z-graph-link': 'z-10',
'z-top-nav': 'z-60',
'z-drawer-backdrop': 'z-90',
'z-drawer-content': 'z-100',
},
[/^bg-glass(:\d+)?$/, ([, opacity = ':75']) => `bg-white${opacity} dark:bg-#111${opacity} backdrop-blur-8`],
],
presets: [
presetWind4(),
presetIcons({ scale: 1.2 }),
presetWebFonts({
fonts: {
sans: 'DM Sans:200,400,700',
mono: 'DM Mono:400,500',
},
processors: createLocalFontProcessor({
fontAssetsDir: './public/assets/fonts',
fontServeBaseUrl: '/assets/fonts',
}),
}),
],
transformers: [
transformerDirectives(),
transformerVariantGroup(),
],
})
```
<!--
Source references:
- https://github.com/antfu/node-modules-inspector/blob/main/packages/node-modules-inspector/src/uno.config.ts
- https://github.com/vitejs/devtools/blob/main/packages/ui/src/unocss/shared-shortcuts.ts
- https://github.com/vitejs/devtools/blob/main/packages/ui/src/unocss/shortcuts.ts
- https://github.com/eslint/config-inspector/blob/main/uno.config.ts
- https://github.com/antfu/vite-plugin-inspect/blob/main/uno.config.ts
- https://github.com/antfu/agent-container/blob/main/hub/uno.config.ts
-->
@@ -0,0 +1,159 @@
---
name: core-starter-kit
description: Framework-agnostic UnoCSS starter setup with class-based utilities, semantic shortcuts, and base light/dark shell styles.
---
# Starter Kit
Use this as a copy-paste baseline for any UnoCSS project (React, Vue, Svelte, Solid, or plain HTML).
## 1) `uno.config.ts`
```ts
import { createLocalFontProcessor } from '@unocss/preset-web-fonts/local'
import {
defineConfig,
presetIcons,
presetWebFonts,
presetWind4,
transformerDirectives,
transformerVariantGroup,
} from 'unocss'
export default defineConfig({
theme: {
fontSize: {
micro: ['0.625rem', '0.875rem'],
mini: ['0.6875rem', '1rem'],
compact: ['0.8125rem', '1.125rem'],
},
colors: {
primary: {
300: '#7CBC71',
400: '#49833E',
600: '#396831',
DEFAULT: '#49833E',
},
},
},
shortcuts: [
{
'color-base': 'color-neutral-800 dark:color-neutral-200',
'bg-base': 'bg-white dark:bg-#111',
'bg-secondary': 'bg-#eee dark:bg-#222',
'border-base': 'border-#8882',
'bg-active': 'bg-#8881',
'color-active': 'color-primary-600 dark:color-primary-300',
'border-active': 'border-primary-600/25 dark:border-primary-400/25',
'btn-action': 'inline-flex items-center gap-2 rounded border border-base px2 py1 op75 hover:op100 hover:bg-active disabled:pointer-events-none disabled:op30!',
'btn-action-sm': 'btn-action text-sm',
'btn-action-icon': 'inline-flex h-8 w-8 items-center justify-center rounded border border-base op75 hover:op100 hover:bg-active disabled:pointer-events-none disabled:op30!',
'glass-panel': 'rounded-lg border border-base bg-glass shadow',
'op-fade': 'op65 dark:op55',
'op-mute': 'op30 dark:op25',
'z-top-nav': 'z-60',
'z-panel-content': 'z-70',
'z-drawer-backdrop': 'z-90',
'z-drawer-content': 'z-100',
'h-nav': 'h-10',
'pad-safe': 'pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]',
},
[/^bg-glass(:\d+)?$/, ([, opacity = ':75']) => `bg-white${opacity} dark:bg-#111${opacity} backdrop-blur-8`],
],
presets: [
presetWind4(),
presetIcons({ scale: 1.2 }),
presetWebFonts({
fonts: {
sans: 'DM Sans:200,400,700',
mono: 'DM Mono:400,500',
},
processors: createLocalFontProcessor({
fontAssetsDir: './public/assets/fonts',
fontServeBaseUrl: '/assets/fonts',
}),
}),
],
transformers: [
transformerDirectives(),
transformerVariantGroup(),
],
})
```
Notes:
- Class-based utilities are the default pattern (no Attributify preset).
- All semantic shortcuts include light/dark-compatible primitives.
## 2) Base global styles (`styles.css`)
```css
html,
body,
#app {
height: 100vh;
height: 100dvh;
margin: 0;
padding: 0;
}
html {
--uno: bg-base color-base font-sans;
color-scheme: light;
}
html.dark {
color-scheme: dark;
background-color: #111;
}
@media (prefers-color-scheme: dark) {
html:not(.light) {
background-color: #111;
}
}
```
## 3) Shell composition example
```html
<div class="w-screen h-screen flex flex-col of-hidden bg-base color-base font-sans pad-safe">
<header class="h-nav shrink-0 flex items-center gap-2 px-3 border-b border-base bg-base z-top-nav">
<button class="btn-action-icon" aria-label="Menu">
<span class="i-ph-list-duotone"></span>
</button>
<span class="font-mono text-xs truncate">workspace-name</span>
</header>
<main class="flex-1 min-h-0 of-auto">
<section class="p-4 flex flex-col gap-3">
<button class="btn-action-sm self-start">
<span class="i-ph-arrow-clockwise-duotone"></span>
refresh
</button>
<div class="glass-panel p-3">
<div class="text-sm">Panel content</div>
<div class="text-mini op-fade mt-1">Secondary metadata</div>
</div>
</section>
</main>
</div>
```
<!--
Source references:
- https://github.com/antfu/node-modules-inspector/blob/main/packages/node-modules-inspector/src/uno.config.ts
- https://github.com/vitejs/devtools/blob/main/packages/ui/src/unocss/index.ts
- https://github.com/vitejs/devtools/blob/main/packages/ui/src/unocss/shared-shortcuts.ts
- https://github.com/eslint/config-inspector/blob/main/uno.config.ts
- https://github.com/antfu/vite-plugin-inspect/blob/main/uno.config.ts
- https://github.com/antfu/agent-container/blob/main/hub/uno.config.ts
- https://github.com/antfu/agent-container/blob/main/hub/app/styles.css
-->
@@ -0,0 +1,126 @@
---
name: core-tokens-and-combinations
description: Common semantic token families and class combinations for UnoCSS-based interfaces.
---
# Tokens and Combinations
## Common Token Families
| Family | Common Tokens | Purpose |
|-------|----------------|---------|
| Surface | `bg-base`, `bg-secondary`, `bg-active`, `bg-tooltip`, `bg-glass` | Main app surfaces, hover/selected state, overlays |
| Text | `color-base`, `color-active`, `op-fade`, `op-mute` | Readability hierarchy and active emphasis |
| Border | `border-base`, `border-active` | Structural and active outlines |
| Action | `btn-action`, `btn-action-sm`, `btn-action-icon` | Unified clickable controls |
| Scale | `color-scale-neutral/low/medium/high/critical` | Visual severity gradients |
| Layout | `h-nav`, `h-tabs`, `pad-safe`, `page-padding` | Stable shell geometry and responsive spacing |
| Depth | `z-*` named shortcuts | Explicit stack order without magic numbers |
## Reusable Shortcuts
```ts
shortcuts: [
{
'btn-action': 'inline-flex items-center gap-2 rounded border border-base px2 py1 op75 hover:op100 hover:bg-active disabled:pointer-events-none disabled:op30!',
'btn-action-sm': 'btn-action text-sm',
'btn-action-icon': 'inline-flex h-8 w-8 items-center justify-center rounded border border-base op75 hover:op100 hover:bg-active disabled:pointer-events-none disabled:op30!',
'glass-panel': 'rounded-lg border border-base bg-glass shadow',
'page-padding': 'pt-24 pl-112 pr-8 pb-8',
'page-padding-collapsed': 'pt-24 pl-14 pr-8 pb-8',
'op-fade': 'op65 dark:op55',
'op-mute': 'op30 dark:op25',
'color-scale-neutral': 'text-gray-700 dark:text-gray-300',
'color-scale-low': 'text-lime-700 dark:text-lime-300',
'color-scale-medium': 'text-amber-700 dark:text-amber-300',
'color-scale-high': 'text-orange-700 dark:text-orange-300',
'color-scale-critical': 'text-red-700 dark:text-red-300',
},
[/^badge-color-(\w+)$/, ([, color]) => `bg-${color}-400:20 dark:bg-${color}-400:10 text-${color}-700 dark:text-${color}-300 border-${color}-600:10 dark:border-${color}-300:10`],
[/^bg-glass(:\d+)?$/, ([, opacity = ':75']) => `bg-white${opacity} dark:bg-#111${opacity} backdrop-blur-8`],
]
```
## Preferred Class Combinations
```html
<header class="h-10 flex items-center gap-2 px-3 border-b border-base bg-base">
<button class="btn-action-icon" aria-label="Open drawer">
<span class="i-ph-list-duotone text-sm"></span>
</button>
<span class="font-mono text-xs truncate">project-name</span>
</header>
<button class="btn-action-sm">
<span class="i-ph-arrow-clockwise-duotone"></span>
<span>refresh</span>
</button>
<span class="inline-flex items-center gap-1 px-1.5 py-px rounded text-micro font-mono bg-active border border-active color-active">
open
</span>
<section class="glass-panel p-3">
<p class="text-sm op-fade">Compact, readable overlay content</p>
</section>
```
## Layer Tokens Example
```ts
shortcuts: [
{
'z-flow-line': 'z--1',
'z-graph-link': 'z-10',
'z-graph-node': 'z-11',
'z-top-nav': 'z-60',
'z-panel-content': 'z-70',
'z-drawer-backdrop': 'z-90',
'z-drawer-content': 'z-100',
},
]
```
## Mobile-Safe Shell Pattern
Use these tokens when building app-like shells with sticky nav, drawers, and nested scroll containers.
```ts
shortcuts: [
{
'h-nav': 'h-10',
'h-tabs': 'h-8',
'pad-safe': 'pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]',
'app-shell': 'w-screen h-screen flex flex-col of-hidden bg-base color-base font-sans',
'scroll-touch': '[-webkit-overflow-scrolling:touch] [overscroll-behavior:contain]',
},
]
```
```html
<div class="app-shell pad-safe">
<header class="h-nav border-b border-base bg-base flex items-center gap-2 px-3">
<button class="btn-action-icon" aria-label="Menu">
<span class="i-ph-list-duotone"></span>
</button>
<span class="font-mono text-xs truncate">workspace-name</span>
</header>
<main class="flex-1 min-h-0 of-auto scroll-touch">
<section class="page-padding">
<p class="text-sm op-fade">content</p>
</section>
</main>
</div>
```
<!--
Source references:
- https://github.com/antfu/node-modules-inspector/blob/main/packages/node-modules-inspector/src/uno.config.ts
- https://github.com/vitejs/devtools/blob/main/packages/ui/src/unocss/shared-shortcuts.ts
- https://github.com/vitejs/devtools/blob/main/packages/ui/src/unocss/shortcuts.ts
- https://github.com/eslint/config-inspector/blob/main/uno.config.ts
- https://github.com/antfu/agent-container/blob/main/hub/uno.config.ts
-->
@@ -0,0 +1,214 @@
---
name: features-data-presentation
description: Preferred rendering patterns for file paths, file icons, relative time, date, numbers, badges, buttons, and icons.
---
# Data Presentation
## Preference Matrix
| Data | Preferred rendering | Class recipe |
|------|----------------------|-------------|
| File path | Mono, segmented emphasis, full value in tooltip | `font-mono truncate` + `title` + segment opacity (`op25/op50/op60`) |
| File icon | Name-first then extension mapping, stable literal classes | literal `i-*` class + semantic tint (`color-blue-500`, `op-fade`) |
| Time ago | Compact relative time in dense rows | `text-micro font-mono tabular-nums op-fade` |
| Date/time | Absolute date in tooltip/detail | `title`/tooltip + `toLocaleString()` |
| Number | Locale-formatted + tabular mono alignment | `text-micro font-mono tabular-nums` |
| Badge | Small semantic chip with low-alpha background | `inline-flex items-center gap-1 px-1.5 py-px rounded border border-emerald-500/40 bg-emerald-500/10 text-micro uppercase tracking-wide` |
| Button | Reuse semantic action shortcuts | `btn-action`, `btn-action-sm`, `btn-action-icon` |
| Icon | Icon + label pair for non-trivial actions | `inline-flex items-center gap-1` |
Use this matrix as default output behavior unless the host project already defines alternatives.
## File Path
- Use `font-mono` for technical identifiers and paths.
- Truncate in layout, but keep full value in `title`.
- Reduce separator and infrastructure segment emphasis (`./`, `/`, `node_modules`, `.pnpm`).
```html
<div class="flex items-center gap-2 min-w-0 text-sm font-mono">
<span class="i-ph-file-ts-duotone color-blue-500 shrink-0"></span>
<span class="truncate" title="/repo/node_modules/.pnpm/react@18.3.1/node_modules/react/index.js">
<span class="op50">./</span>
<span class="op60">node_modules/</span>
<span class="op25">.pnpm/</span>
<span class="color-active">react</span>
<span class="op50">/index.js</span>
</span>
</div>
```
## File Icon
- Map filename first (`dockerfile`, `.env`, `.gitignore`), then extension.
- Keep icon classes literal so UnoCSS can statically extract them.
- Return both icon and tint classes.
```ts
// @unocss-include
const byName = {
'dockerfile': { icon: 'i-ph-file-cloud-duotone', color: 'color-sky-500' },
'.env': { icon: 'i-ph-file-lock-duotone', color: 'color-amber-600 dark:color-amber-400' },
}
const byExt = {
ts: { icon: 'i-ph-file-ts-duotone', color: 'color-blue-500' },
js: { icon: 'i-ph-file-js-duotone', color: 'color-yellow-500' },
vue: { icon: 'i-ph-file-vue-duotone', color: 'color-emerald-500' },
md: { icon: 'i-ph-file-md-duotone', color: 'op-fade' },
}
export function fileIcon(path: string) {
const base = (path.split('/').pop() || path).toLowerCase()
const byFilename = byName[base as keyof typeof byName]
if (byFilename)
return byFilename
const ext = base.includes('.') ? base.slice(base.lastIndexOf('.') + 1) : ''
return byExt[ext as keyof typeof byExt] || { icon: 'i-ph-file-duotone', color: 'op-fade' }
}
```
## Time and Date
- Show compact relative age in dense lists (`45s`, `8m`, `3h`, `12d`, `2mo`, `1y`).
- Show absolute date/time on hover or detail rows.
- Use `tabular-nums font-mono` for changing numbers to avoid layout jitter.
```ts
export function formatAge(ts: number): string {
const s = Math.max(0, Math.floor((Date.now() - ts) / 1000))
if (s < 60) return `${s}s`
const m = Math.floor(s / 60)
if (m < 60) return `${m}m`
const h = Math.floor(m / 60)
if (h < 24) return `${h}h`
const d = Math.floor(h / 24)
if (d < 30) return `${d}d`
const mo = Math.floor(d / 30)
if (mo < 12) return `${mo}mo`
return `${Math.floor(d / 365)}y`
}
export const formatDateTime = (ts: number) => new Date(ts).toLocaleString()
```
```html
<time class="font-mono text-micro tabular-nums op-fade" title="2026-06-22 09:14:12">
8m
</time>
```
## Number
- Use locale-aware formatting for counts and percentages.
- Use explicit currency formatting for money.
- Render dense metrics as mono + tabular.
```ts
const countLabel = count.toLocaleString()
const percentLabel = ratio.toLocaleString(undefined, {
style: 'percent',
minimumFractionDigits: 1,
maximumFractionDigits: 1,
})
const costLabel = new Intl.NumberFormat(undefined, {
style: 'currency',
currency: 'USD',
}).format(cost)
```
```html
<span class="text-micro font-mono tabular-nums op-mute">12,480</span>
```
## Badges, Buttons, Icons
- Status badges: small, uppercase or mono, tinted border and text, low-alpha background.
- Buttons: unify around `btn-action*` shortcuts.
- Icons: pair icon + text, keep icon size near text size (`text-sm` or `text-[0.9em]`).
### Icon System Strategy
- Use one main icon family for controls and actions (for consistent stroke/weight).
- Use specialized file-type icon sets for path/file lists.
- Keep file icons tinted by category, but keep control icons mostly semantic (`color-active`, `op-fade`, status colors).
```ts
// Controls/actions
const controls = {
refresh: 'i-ph-arrow-clockwise-duotone',
close: 'i-ph-x',
openExternal: 'i-octicon-link-external-16',
}
// File types
const fileTypes = {
ts: 'i-ph-file-ts-duotone color-blue-500',
js: 'i-ph-file-js-duotone color-yellow-500',
md: 'i-ph-file-md-duotone op-fade',
}
```
```html
<span class="inline-flex items-center gap-1 px-1.5 py-px rounded border border-emerald-500/40 text-emerald-700 dark:text-emerald-300 bg-emerald-500/10 text-micro uppercase tracking-wide">
<span class="i-octicon-check-circle-16 text-[0.9em]"></span>
open
</span>
<button class="btn-action-sm">
<span class="i-ph-arrow-clockwise-duotone"></span>
refresh
</button>
<a class="btn-action-icon" href="#" aria-label="Open on GitHub">
<span class="i-octicon-mark-github-16 text-sm"></span>
</a>
```
## Centralize Status Glyph Mapping
- Keep one source of truth for status icon, color, and label.
- Return literal class strings from mapping functions so UnoCSS can extract dynamic icon classes.
- Reuse mapping across list rows, headers, tabs, and tooltips.
```ts
// @unocss-include
type SessionStatus = 'thinking' | 'tool' | 'task' | 'questions' | 'error' | 'idle'
interface StatusGlyph {
icon: string
color: string
label: string
}
export function sessionStatusGlyph(status: SessionStatus): StatusGlyph {
switch (status) {
case 'thinking':
return { icon: 'i-ph-spinner-duotone animate-spin', color: 'color-amber-500', label: 'thinking' }
case 'tool':
return { icon: 'i-ph-gear-duotone animate-spin', color: 'color-orange-500', label: 'running a tool' }
case 'error':
return { icon: 'i-ph-warning-circle-duotone', color: 'color-red-500', label: 'error' }
default:
return { icon: 'i-ph-check-circle-duotone', color: 'color-emerald-500/70', label: 'idle' }
}
}
```
<!--
Source references:
- https://github.com/vitejs/devtools/blob/main/packages/rolldown/src/app/components/display/HighlightedPath.ts
- https://github.com/vitejs/devtools/blob/main/packages/rolldown/src/app/components/display/ModuleId.vue
- https://github.com/vitejs/devtools/blob/main/packages/rolldown/src/app/utils/format.ts
- https://github.com/eslint/config-inspector/blob/main/app/components/FileItem.vue
- https://github.com/antfu/vite-plugin-inspect/blob/main/src/client/components/ModuleId.vue
- https://github.com/antfu/node-modules-inspector/blob/main/packages/node-modules-inspector/src/app/components/display/DateBadge.vue
- https://github.com/antfu/node-modules-inspector/blob/main/packages/node-modules-inspector/src/app/components/display/DurationBadge.vue
- https://github.com/antfu/node-modules-inspector/blob/main/packages/node-modules-inspector/src/app/components/display/NumberBadge.vue
- https://github.com/antfu/agent-container/blob/main/hub/app/components/FileView.vue
- https://github.com/antfu/agent-container/blob/main/hub/app/components/GitView.vue
- https://github.com/antfu/agent-container/blob/main/hub/app/components/native/ContextIndicator.vue
- https://github.com/antfu/agent-container/blob/main/hub/app/state/status.ts
-->
@@ -0,0 +1,81 @@
---
name: features-floating-vue-overrides
description: Floating Vue setup and style overrides aligned with UnoCSS semantic tokens.
---
# Floating Vue Overrides
Use this when a Vue app uses `floating-vue`. For non-Vue tooltip libraries, apply the same semantic tokens to popover containers and arrows.
## Runtime Setup
```ts
import FloatingVue from 'floating-vue'
import 'floating-vue/dist/style.css'
app.use(FloatingVue, {
overflowPadding: 20,
})
```
## Shared Popper Styling
```css
.v-popper--theme-dropdown .v-popper__inner,
.v-popper--theme-tooltip .v-popper__inner {
@apply bg-tooltip color-base font-sans rounded border border-base shadow dark:shadow-2xl;
box-shadow: 0 6px 30px #0000001a;
}
.v-popper--theme-tooltip .v-popper__inner {
@apply text-sm;
}
.v-popper--theme-tooltip {
max-width: 20rem;
}
.v-popper--theme-tooltip .v-popper__arrow-inner,
.v-popper--theme-dropdown .v-popper__arrow-inner {
visibility: visible;
@apply border-white dark:border-#111;
}
.v-popper--theme-tooltip .v-popper__arrow-outer,
.v-popper--theme-dropdown .v-popper__arrow-outer {
@apply border-base;
}
.v-popper--theme-tooltip.v-popper--shown,
.v-popper--theme-tooltip.v-popper--shown * {
transition: none !important;
}
```
## Token Contract for Any Popover Library
- `bg-tooltip`: translucent light/dark surface + backdrop blur.
- `border-base`: low-contrast border that works in both themes.
- `color-base`: readable foreground for both themes.
- keep no-op transitions for tooltips to avoid jitter in dense UIs.
If your library supports custom class names, apply these tokens directly:
```html
<div class="bg-tooltip color-base border border-base rounded shadow text-sm">
tooltip content
</div>
```
<!--
Source references:
- https://github.com/antfu/node-modules-inspector/blob/main/packages/node-modules-inspector/src/app/plugins/floating-vue.ts
- https://github.com/antfu/node-modules-inspector/blob/main/packages/node-modules-inspector/src/app/styles/global.css
- https://github.com/vitejs/devtools/blob/main/packages/rolldown/src/app/plugins/floating-vue.ts
- https://github.com/vitejs/devtools/blob/main/packages/rolldown/src/app/styles/global.css
- https://github.com/eslint/config-inspector/blob/main/app/plugins/floating-vue.ts
- https://github.com/eslint/config-inspector/blob/main/app/styles/global.css
- https://github.com/antfu/vite-plugin-inspect/blob/main/src/client/styles/main.css
- https://github.com/antfu/agent-container/blob/main/hub/app/plugins/floating-vue.client.ts
- https://github.com/antfu/agent-container/blob/main/hub/app/styles.css
-->
@@ -0,0 +1,79 @@
---
name: features-micro-interactions
description: Small details that make interfaces feel polished. Concentric radius, optical alignment, borders vs shadows by context, staggered enters, subtle exits, tabular numbers, text wrapping, image outlines, tap scale, and hit areas, expressed as UnoCSS classes.
---
# Micro-Interaction Polish
Great interfaces are a stack of small details. These are framework-agnostic; the class names use UnoCSS, but the principles apply to any stack.
## Additive config
Add these when a project needs the polish layer. They are additive; do not force them onto dense devtools surfaces that intentionally stay flat.
```ts
export default defineConfig({
theme: {
boxShadow: {
// Layered, background-tinted elevation for floating or marketing surfaces.
card: '0 1px 2px rgb(0 0 0 / 0.04), 0 8px 24px rgb(0 0 0 / 0.08)',
},
},
shortcuts: [
{
// Subtle image edge. Pure black in light, pure white in dark, never a tinted neutral.
'img-outline': 'outline outline-1 outline-black/10 dark:outline-white/10 outline-offset-[-1px]',
// Tactile press feedback.
'tap-scale': 'transition-transform active:scale-[0.96]',
},
],
})
```
## Borders vs shadows (by context)
antfu tooling UIs are deliberately flat and border-driven. Marketing and floating surfaces read better with layered shadows.
- **Dense, devtools, structural**: keep `border-base` hairlines. Flat is correct here.
- **Elevated, floating, marketing cards**: use `shadow-card` (layered, transparent, background-tinted), not a hard 1px border. Shadows adapt to any background; solid borders do not.
- Never animate `box-shadow` for hover depth on large lists; swap opacity or use a pseudo-element.
## Concentric border radius
Outer radius = inner radius + padding. Mismatched nested radii is the most common reason a card feels off. A `rounded-2xl` card with `p-2` should hold a `rounded-lg` child, not another `rounded-2xl`.
## Optical over geometric alignment
When geometric centering looks off, align optically. Play triangles, single icons in round buttons, and asymmetric glyphs usually need a 1px nudge (`pl-px`) or a fix to the SVG viewBox.
## Animation
- **Interruptible state changes**: use CSS transitions for hover, active, and toggle so they can reverse mid-flight. Reserve keyframes for staged sequences that run once.
- **Never `transition: all`**: specify exact properties (`transition-property: opacity, transform`). UnoCSS `transition-transform` already covers transform, translate, scale, and rotate.
- **Split and stagger enters**: do not animate one container. Break content into chunks and stagger each by about 100ms (`animation-delay: calc(var(--i) * 100ms)` or a stagger in a motion library).
- **Subtle exits**: exit with a small fixed `translateY` and a fade, softer than the enter.
- **Skip animation on first load**: do not replay enter animations on initial render (in a motion library, the equivalent of `initial={false}`).
- **Icon swaps**: cross-fade with opacity, scale, and a little blur rather than toggling visibility. Values: scale `0.25` to `1`, opacity `0` to `1`, blur `4px` to `0`. With a motion library use a spring with `bounce: 0`; without one, keep both icons in the DOM (one absolutely positioned) and cross-fade with `cubic-bezier(0.2, 0, 0, 1)`.
- **Tap feedback**: `tap-scale` gives `scale(0.96)` on press. Never go below `0.95`; it looks exaggerated. Offer a way to disable it where motion would distract.
- Any motion beyond hover and active must honor `prefers-reduced-motion`.
## Text and numbers
- **Tabular numbers**: any dynamically updating number uses `tabular-nums` to prevent layout shift. antfu already pairs this with `font-mono` for technical values.
- **Heading wrap**: `text-balance` on headings. **Body wrap**: `text-pretty` to avoid orphans. These map to `text-wrap: balance` and `text-wrap: pretty`.
- **Font smoothing**: apply `antialiased` to the root on macOS for crisper text.
## Images
- Give photos a subtle `img-outline`. The outline color must be pure black in light mode and pure white in dark mode at low opacity, never a tinted neutral, which reads as dirt on the edge.
## Performance and hit area
- **`will-change` sparingly**: only on elements that actually animate, and only for `transform`, `opacity`, `filter`. Never `will-change: all`. Add it only when you see first-frame stutter.
- Animate only `transform` and `opacity` for movement. Never animate `top`, `left`, `width`, or `height`.
- **Minimum hit area**: interactive controls need at least 40x40px. Extend a small visible control with a pseudo-element. Do not let two hit areas overlap.
<!--
Source references:
- https://github.com/jakubkrehel/make-interfaces-feel-better/tree/main/skills/make-interfaces-feel-better
-->