@antfu/design

中文文档
0.6.0发布于 2026/10/01MITES Module构建来源已验证近 30 天下载 3932

A customizable, composable design system for devtools-style Vue apps — a UnoCSS preset, Vue primitives, a design skill, and an a11y contrast check

原始使用文档
文档目录19 节
  1. Install
  2. Quick start
  3. Exports
  4. Migrating an existing app
  5. Embedding in a shadow root
  6. Accessibility
  7. Components
  8. Action
  9. Display
  10. Form
  11. Overlay
  12. Layout
  13. Feedback
  14. Tokens
  15. Semantic & composite shortcuts
  16. Severity scale
  17. Type sizes
  18. Dynamic
  19. License

@antfu/design

A customizable, composable design system for devtools-style Vue apps: a UnoCSS preset (presetAnthonyDesign), a set of Vue primitives, a ground-up design skill, and a color-contrast a11y check. Something in between a component library and shadcn.

Install

pnpm add @antfu/design unocss vue

Quick start

// uno.config.ts
import { presetAnthonyDesign } from '@antfu/design/unocss'
import transformerDirectives from '@unocss/transformer-directives'
import { defineConfig, presetIcons, presetWebFonts, presetWind4 } from 'unocss'

export default defineConfig({
  presets: [
    presetAnthonyDesign({ primary: '#49833E' }),
    presetWind4(), // a base preset is required — bring your own
    presetIcons(),
    presetWebFonts({ fonts: { sans: 'DM Sans', mono: 'DM Mono' } }),
  ],
  // Required — most shipped styles recolor overlays with token `--at-apply`
  // directives; this transformer expands them (and lets you reuse tokens in CSS).
  // (vue-afloat.css is the exception — it themes via vue-afloat's own
  // `--vue-afloat-*` CSS variables and needs no transformer.)
  transformers: [transformerDirectives()],
  // The preset ships no z-index scale (stacking is the app's to own) and blocks
  // plain `z-<number>`. Define the named layers the overlay components use here —
  // these values are yours; tune them to fit your app's stack.
  shortcuts: {
    'z-nav': 'z-[30]',
    'z-dropdown': 'z-[40]',
    'z-tooltip': 'z-[45]',
    'z-toast': 'z-[50]',
    'z-modal-backdrop': 'z-[60]',
    'z-modal-content': 'z-[70]',
    'z-drawer-backdrop': 'z-[80]',
    'z-drawer-content': 'z-[90]',
  },
})

Pair it with @unocss/eslint-plugin for the feedback loop — it surfaces the blocklist hints (e.g. a plain z-50) in your editor and CI instead of silently dropping at build time.

// Components are imported by full path (no barrel) — categorized and prefixed:
import ActionButton from '@antfu/design/components/Action/ActionButton.vue'
import DisplayBadge from '@antfu/design/components/Display/DisplayBadge.vue'
import OverlayModal from '@antfu/design/components/Overlay/OverlayModal.vue'
import '@antfu/design/styles.css' // everything, incl. splitpanes/reka/vue-afloat overrides

styles.css bundles every overlay engine's override. If you don't use them all, cherry-pick instead so unused (splitpanes/reka-ui) CSS never ships:

import '@antfu/design/styles/base.css'
import '@antfu/design/styles/scrollbar.css'
import '@antfu/design/styles/vue-afloat.css' // only if you use tooltips

The package ships raw .ts / .vue source (no bundling) — your build compiles it. No extra content config is needed: UnoCSS's default scan matches .vue/.tsx by extension (its only default exclude is CSS, not node_modules), so the components you import are picked up automatically. If you do set content.pipeline.include, note it replaces the default scan rather than extending it — restate the defaults too, or your own sources stop being generated.

It's a single preset that is not self-contained: it contributes only the antfu design layer (theme tokens, semantic shortcuts, dynamic rules, severity). You compose the base preset, icons, fonts and reset yourself — and add @unocss/transformer-directives (the shipped styles use token --at-apply directives) plus, recommended, @unocss/eslint-plugin for the feedback loop.

Exports

SubpathWhat
./components/*one readable .vue per component (e.g. ./components/Display/DisplayBadge.vue)
./unocssthe single presetAnthonyDesign preset
./utilscolor, format, path, semver, contrast, keybinding helpers (pure, stateless)
./composables/*Vue helpers: colorScheme (opt-in scheme context) and toast (useToast queue)
./a11yprogrammatic color-contrast scan
./styles.css, ./styles/*all styles, or per-concern files
./splitpanes.d.tsopt-in fallback types for older splitpanes (v4.1.2+ ships its own)

The package is stateless — no dark-mode/clipboard/toast state. Components that vary by scheme take a colorScheme prop; toasts are controlled. Use VueUse directly for state.

Two opt-in helpers reduce the boilerplate without adding global state: provideColorScheme(() => isDark ? 'dark' : 'light') (from @antfu/design/composables/colorScheme) lets scheme-aware components inherit the scheme instead of threading a prop, and useToast() (from @antfu/design/composables/toast) owns a toast queue for FeedbackToasts. Both only read state you own.

Migrating an existing app

If your app predates the design system, adoption is mostly deletion:

  • Delete your local token shortcuts. Apps already on the *-base dialect (bg-base, color-base, border-base, op-fade, btn-action, badge-color-*, color-scale-*, …) can drop those hand-rolled uno.config.ts shortcuts blocks — often 20–170 lines — and inherit them from presetAnthonyDesign. Set primary / darkBackground to your existing values (and keep your existing presetWebFonts config as-is — this preset has no opinion on fonts) for a near-zero visual diff.
  • Dedupe local utilities into @antfu/design/utils: the duplicated getHashColorFromString / getPluginColor, the byte / duration / time-ago formatters, module-id / path parsing, and semver-range parsing all live there.
  • Bump off the legacy presetUno alias. presetUno is the old name for presetWind3; switch to a current base (presetWind4 recommended, or presetWind3) so the semantic shortcuts have utilities to expand into. You still bring your own icons, fonts, and @unocss/reset — the preset bundles none.
  • Thread the scheme, not state. The package owns no dark/toast state: pass :color-scheme (or call provideColorScheme() once at the root) and move imperative toast singletons onto a useToast() queue.

Then follow the redesign protocol in the skill (advanced-patterns): map raw colors → tokens, swap raw elements for primitives one family at a time, and run the contrast scan in light + dark after each step.

Embedding in a shadow root

Mounting the components inside a shadow root — a web component, a devtools dock, an injected overlay — is supported, but two things do not cross the boundary on their own:

  • The scheme class goes on an ancestor, never on the element you style. The dark variant compiles to a descendant selector (.dark .bg-base), so a node that is .dark doesn't match its own dark rules — the subtree renders light-themed inside a dark host. Toggle dark/light (and color-scheme) on a wrapper — display: contents works, so it costs no layout — and put the surface utilities on its child. :host(.dark) is not an alternative: these are descendant selectors, and they can't reach across the boundary.
  • Global CSS can't pierce the boundary — inline the styles. @antfu/design/styles/* (scrollbar, reka-ui animations, vue-afloat, …) must be part of the sheet you inject into the root, alongside the UnoCSS output. They're written with * / unscoped selectors precisely so they work in either place. Add scrollbar-gutter: stable on your own scroll container if you want the gutter reserved.

Portaled content needs no setup. Every component that teleports — the dialogs (OverlayModal, OverlayDrawer, OverlayConfirm), the poppers (FormSelect, FormCombobox, OverlayDropdown, OverlayHoverCard, OverlayContextMenu, LayoutMenubar) and FeedbackToasts — detects the enclosing shadow root and teleports into a container appended to it, which also mirrors the dark/light class of the nearest scheme wrapper so dark-mode tokens keep resolving there. Pass an explicit to (a selector or element) to override the target. The Utilities/ShadowRoot story mounts them all inside a shadow root.

Accessibility

A color-contrast scan (axe-core + Playwright) runs a URL in light and dark mode. Use it programmatically:

import { formatContrastReport, runContrastScan } from '@antfu/design/a11y'

const result = await runContrastScan({ url: 'http://localhost:6006/iframe.html' })
console.log(formatContrastReport(result))

…or run the bundled script with tsx:

tsx node_modules/@antfu/design/a11y/cli.ts http://localhost:6006/iframe.html

Components

Action

ComponentUse it for
ActionButtonactions. variant action/primary/text, polymorphic href/as, icon, loading.
ActionIconButtona round icon-only button with a tooltip, active state, #badge. Compose with VueUse's useDark for a dark toggle.
ActionTogglea single pressed/unpressed toggle button (icon, label).
ActionToggleGroupa segmented set of toggles from options, single- or multi-select (multiple), optionally icon-only.

Display

ComponentUse it for
DisplayAvataran image avatar that falls back to hash-colored initials from name when src is absent/fails.
DisplayBadgea status/type/tag chip — hash-colored from text, a palette name (color="green"), a hue, or muted. colorScheme, variant, icon, rounded (md/full/em number), paddingX/paddingY; sized by its own font-size.
DisplayByteshumanized size, colorize, percent of total.
DisplayDaterelative time + exact-date tooltip, colorize by age, live.
DisplayDonuta progress ring.
DisplayDurationms → human, colorize by severity.
DisplayFileIconext/path → icon via a configurable rule list (rules, folderRules), with an invert escape hatch for dark icon sets on light surfaces.
DisplayFilePathtruncates, dims directories, decodes .pnpm, icon, link.
DisplayIconifyRemoteIconrenders an Iconify icon by name without a bundled icon set, fetching from the Iconify API.
DisplayKbdrenders a chord string (e.g. mod+k) as platform glyphs.
DisplayKeyValuea labeled stat — inline row or stacked, mono value, #badge adornment.
DisplayLabela fully-rounded DisplayBadge (tighter padding) for GitHub-style tags (colorScheme).
DisplayNumberformatted numbers (Intl), mono + tabular, prefix/suffix.
DisplayNumberBadgea DisplayNumber in a badge shell — a count pill.
DisplayPackageNamescope colored by hash (colorScheme).
DisplayProgressBara linear progress track — determinate (value 0–1) or indeterminate when omitted, color, rounded.
DisplayProportionBara stacked proportion bar.
DisplaySafeImagean <img> that swaps to the #fallback slot on a missing/failed src, with an optional preload to avoid a flash.
DisplayTreenests a flat items list (via getPath) or pre-built nodes into an expandable file/folder-style tree, with #leaf/#default slot overrides.
DisplayVersionvX.Y.Z prefix logic; passes specs/ranges (workspace:*, ^1.2) through untouched.

Form

ComponentUse it for
FormCheckboxa checkbox control bound with v-model.
FormComboboxa searchable, filterable select (reka-ui Combobox) over an options list.
FormFieldwraps a control with a label, description, and error (role="alert"), required marker.
FormNumberInputa numeric input with min/max/step clamping and optional −/+ stepper controls.
FormRadioGroupa radio-button group bound with v-model.
FormSearchFielda search input — icon + DisplayKbd shortcut hint + clear button.
FormSegmentedControla single-select segmented control (reka-ui RadioGroup) over an options list, including a valid null segment.
FormSelecta native-feeling select bound with v-model.
FormSwitchan on/off switch bound with v-model.
FormTextInputa single-line text input, icon, invalid state.
FormTextareaa multi-line text input, rows, invalid state, resize toggle.

Overlay

ComponentUse it for
OverlayConfirman AlertDialog for a confirm/cancel decision, variant="danger" for destructive actions.
OverlayContextMenu (+ OverlayContextMenuItem, OverlayContextMenuLabel, OverlayContextMenuSeparator)a right-click menu.
OverlayDrawera slide-in panel from any side (left/right/top/bottom), sized by width.
OverlayDropdown (+ OverlayDropdownCheckboxItem, OverlayDropdownGroup, OverlayDropdownLabel, OverlayDropdownRadioGroup, OverlayDropdownRadioItem, OverlayDropdownSeparator, OverlayDropdownSub)a trigger-anchored dropdown menu.
OverlayDropdownItema selectable dropdown row — icon, variant="danger", trailing shortcut (rendered via DisplayKbd).
OverlayHoverCarda hover-triggered popover with configurable openDelay/closeDelay.
OverlayModala centered dialog (title, description, #trigger slot).
OverlayTooltipa vue-afloat tooltip — text content or the #content slot, configurable placement/delay/triggers, plus a virtual-anchor mode for canvas/graph overlays.

Layout

ComponentUse it for
LayoutAccordioncollapsible sections from items; one entry = a single section, multiple for several open at once.
LayoutBreadcrumba linked path trail from items; the last crumb renders as plain text.
LayoutCarda bordered content surface.
LayoutDataTablea sortable, generic data table from columns + rows, optional stickyHeader.
LayoutExpandableList"show N more" truncation for a list.
LayoutMenubara desktop-style menu bar from a menus list of MenubarMenuEntry.
LayoutPaginationpage-number navigation.
LayoutPanelGridsa dots/grid background pattern surface for empty states or canvases.
LayoutScrollAreaa custom-scrollbar scroll container.
LayoutSeparatora horizontal/vertical divider, optionally with a centered label.
LayoutSideNava vertical nav list from items, active-item highlighting via v-model.
LayoutSplitPaneresizable split panes (wraps splitpanes).
LayoutTabsunderline/segment tabs, count chips.
LayoutToolbara sticky, glass-surfaced action bar with #start/#search/#end slots.
LayoutVirtualLista virtualized list (@tanstack/vue-virtual) for large collections.

Feedback

ComponentUse it for
FeedbackEmptyStatean empty/zero-state placeholder — icon, title, description, #action slot.
FeedbackLoadinga loading placeholder wrapping FeedbackSpinner.
FeedbackSkeletona shimmer placeholder block for loading content.
FeedbackSpinnera spinner glyph.
FeedbackTipan inline callout — info/success/warning/error.
FeedbackToastsa presentational toast stack (items, position) — the app owns the list and ids via its own useToast().

Full catalog with import paths: core-components.md.

Tokens

Semantic & composite shortcuts

TokenExpands to
color-basecolor-base-light dark:color-base-dark
color-base-lightcolor-neutral-800
color-base-darkcolor-neutral-200
color-mutedcolor-neutral-600 dark:color-neutral-400
color-faintcolor-neutral-500 dark:color-neutral-500
color-activecolor-primary-600 dark:color-primary-300
bg-basebg-white dark:bg-#111
bg-secondarybg-#f6f6f6 dark:bg-#101010
bg-raisedbg-white/65 dark:bg-black/60
bg-sunkenbg-black/4 dark:bg-white/6
bg-activebg-#99999920
bg-ambientbg-#99999925
bg-hoverbg-#99999915
bg-codebg-gray-500/5
bg-tooltipbg-white/75 dark:bg-#111/75 backdrop-blur-8
bg-gradient-morebg-gradient-to-t from-white via-white/80 to-white/0 dark:from-#111 dark:via-#111/80 dark:to-#111/0
border-baseborder-#9992
border-muteborder-#9991
border-activeborder-primary-600/25 dark:border-primary-400/25
ring-basering-#9992
op-fadeop65
op-muteop40
op-activeop100
op-disabledop30
icon-catppuccininvert-100 hue-rotate-180 brightness-80 dark:invert-0 dark:hue-rotate-0 dark:brightness-100
btn-actionborder border-base rounded flex gap-2 items-center px2 py1 op75 hover:op100 hover:bg-hover transition disabled:pointer-events-none disabled:op30! outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40
btn-action-smbtn-action text-sm
btn-action-activecolor-active border-active! bg-active op100!
btn-iconw-9 h-9 rounded-full op-fade hover:op100 hover:bg-hover transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40
btn-icon-compactw-6 h-6 rounded op-fade hover:op100 hover:bg-hover transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40
btn-icon-squarew-9 h-9 rounded border border-base op-fade hover:op100 hover:bg-hover transition flex items-center justify-center disabled:pointer-events-none disabled:op30 outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40
btn-primarypx2 py1 rounded border border-transparent flex gap-2 items-center bg-primary-500 hover:bg-primary-600 text-white transition disabled:op50 disabled:pointer-events-none outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40
btn-textpx2 py1 rounded border border-transparent inline-flex gap-2 items-center op75 hover:op100 hover:bg-hover transition disabled:pointer-events-none disabled:op30! outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40
badgeinline-flex items-center gap-1 px-1.5 py-0.5 rounded-md text-xs font-medium leading-none
badge-activebadge bg-active color-active
badge-mutedbadge bg-#8881 color-muted
pad-safe-tpt-[env(safe-area-inset-top)]
pad-safe-rpr-[env(safe-area-inset-right)]
pad-safe-bpb-[env(safe-area-inset-bottom)]
pad-safe-lpl-[env(safe-area-inset-left)]
pad-safe-xpad-safe-l pad-safe-r
pad-safe-ypad-safe-t pad-safe-b
pad-safepad-safe-x pad-safe-y

Severity scale

TokenExpands to
color-scale-neutraltext-gray-700 dark:text-gray-300
color-scale-lowtext-lime-700 dark:text-lime-300 dark:saturate-75
color-scale-mediumtext-amber-700 dark:text-amber-300 dark:saturate-90
color-scale-hightext-orange-700 dark:text-orange-300
color-scale-criticaltext-red-700 dark:text-red-300

Type sizes

TokenExpands to
text-microtext-[10px] leading-[1.4]
text-minitext-[11px] leading-[1.45]
text-compacttext-[12px] leading-[1.5]

Dynamic

TokenExpands to
badge-color-<name>a chip tinted by any palette color name (dark-aware)
bg-glass / bg-glass:<n>translucent surface + backdrop-blur
bg-dots / bg-dots-<n>radial dot-grid background, variable cell size in px (default 16)
bg-grid / bg-grid-<n>crosshatch grid-lines background, variable cell size in px (default 16)
scroll-fade / scroll-fade-{x,y,t,b,l,r,s,e}scroll-aware edge fade (scroll-fade-<n>, scroll-fade-none); pairs with no-scrollbar
shimmer / shimmer-{once,reverse,none}text shimmer over currentColor (shimmer-{color,duration,spread,angle}-*)

License

MIT © Anthony Fu

版本与发布记录

@antfu/design 共发布 16 个版本,首次发布于 2026/06/28;过去 12 个月发布了 16 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.6.0latest2026/10/011 个月前
0.5.32026/09/211 个月前
0.5.22026/09/161 个月前
0.5.12026/09/161 个月前
0.5.02026/09/141 个月前
0.4.02026/08/202 个月前

关于 @antfu/design 的常见问题

@antfu/design 是什么?

A customizable, composable design system for devtools-style Vue apps — a UnoCSS preset, Vue primitives, a design skill, and an a11y contrast check

如何安装 @antfu/design?

在项目目录运行 npm install @antfu/design,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:vue(^3.5.0)、unocss(>=66.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@antfu/design 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/antfu__design 是否存在。

@antfu/design 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@antfu/design 可以商用吗?

@antfu/design 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@antfu/design 的下载量有多少?

根据 npm 官方下载统计,@antfu/design 在 2026/10/06 – 2026/11/04 这 30 天内被下载 3,932 次,日均约 131 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@antfu/design 还在维护吗?

最新版本 0.6.0 发布于 2026/10/01(1 个月前);至今共发布 16 个版本,其中过去 12 个月发布了 16 个;首次发布于 2026/06/28。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。