@basenative/theme

中文文档
3.3.0发布于 2026/11/03Apache-2.0TypeScriptES Module零依赖构建来源已验证近 30 天下载 2029

@basenative/components背后的主题层——一个四层令牌形状,一个构建失败的WCAG对比控制,一个单网格图标规则和CTI+桥

中文使用文档
文档目录15 节
  1. 开始新属性
  2. 1. 定义
  3. 2. 写出主题
  4. 3. 为它服务
  5. 4. 打开门
  6. CI会失败的
  7. 一揽子计划背后的四项决定
  8. 为什么没有默认主题
  9. 信号家庭对五个插槽
  10. 黑暗主题:可选,但承诺不是
  11. 为什么桥住在这里
  12. 图标
  13. 对比度, 直接
  14. 线性
  15. 许可证

@ asinative/ 主题

驱动的主题层 @basenative/components。 。 。 .

一个图书馆,很多主题. DuganLabs的每个财产都有自己的身份 停止重新解析对比度、 符号级、 图标网格和 CSS 密接 刮伤。 这个包装的是机器,而不是外观 在这里,不会有一个.

零生产依赖. 手写JS,手写类型, node --test。 。 。 .

@basenative/theme            defineTheme, validateTheme, ThemeError, toCss, bridgeToCti
@basenative/theme/contrast   relativeLuminance, contrastRatio, parseCustomProperties,
                             resolveColor, AA_TEXT, AA_LARGE, AA_NONTEXT
@basenative/theme/audit      auditPairings, assertPairings, defaultPairings,
                             assertThemeContrast, ContrastError
@basenative/theme/css        minifyCss
@basenative/theme/icons      defineIconSet, renderIcon, validateIconSet, IconSetError
@basenative/theme/bridge.css the CTI+ bridge, as a stylesheet

开始新属性

1. 定义

一个主题是四层。 没有东西可以跳过一个, 只有一级包含一个六分之一.

级别你写示例
1. 联合国. ramps字义 #rrggbb 比额表neutral, (中文(简体) ), primary,加上你需要的信号
2. 联合国. surfaces 编号 : ink什么东西坐 打开上面写着什么surfaces.sunk: 'neutral.100'
3个. roles什么样的元素 实值roles.borderControl: 'neutral.500'
4.四. signals家庭状况  词汇, 分配给五个组件槽families.stalled, (中文(简体) ), slots.warning: 'stalled'

2 -4级从不携带螺旋. 他们携带 '<ramp>.<step>',作为 var() 链并解回由校验器解回文字。 换个斜坡 台阶和上面的一切都会移动.

两道坡道是强制性的,因为 @basenative/components 直接读取: primary (第50-900步)和 neutral (步骤50-950).

2. 写出主题

这是开始者。 没错 @basenative/components' 自己的默认调色板 译名而非新相貌 两道坡道是你第一件事 它不是从此软件包导出 目的(见 为什么没有默认主题(原始内容存档于2018-03-26) (英语). copy it.

import { defineTheme } from '@basenative/theme';

export const theme = defineTheme({
  name: 'starter',
  scheme: 'light',

  // Tier 1 — the only hexes in the file.
  ramps: {
    neutral: {
      50: '#fafafa',
      100: '#f4f4f5',
      200: '#e4e4e7',
      300: '#d4d4d8',
      400: '#a1a1aa',
      500: '#71717a',
      600: '#52525b',
      700: '#3f3f46',
      800: '#27272a',
      900: '#18181b',
      950: '#09090b',
    },
    primary: {
      50: '#eff6ff',
      100: '#dbeafe',
      200: '#bfdbfe',
      300: '#93c5fd',
      400: '#60a5fa',
      500: '#3b82f6',
      600: '#2563eb',
      700: '#1d4ed8',
      800: '#1e40af',
      900: '#1e3a8a',
    },
    success: { 100: '#dcfce7', 600: '#16a34a', 700: '#15803d', 800: '#166534' },
    warning: { 100: '#fef3c7', 600: '#d97706', 700: '#b45309', 800: '#92400e' },
    danger: { 100: '#fee2e2', 500: '#ef4444', 600: '#dc2626', 800: '#991b1b' },
    paper: { white: '#ffffff' },
  },

  // Tier 2 — surfaces, and the ink that goes on them.
  surfaces: {
    base: 'paper.white',
    subtle: 'neutral.50',
    sunk: 'neutral.100',
    raised: 'paper.white',
    inverse: 'neutral.900',
  },
  ink: {
    base: 'neutral.900',
    // Upstream's muted ink is gray-500, which is 4.40:1 on the gray-100 sunk
    // surface. The harness refused it; one step darker clears at 7.03:1.
    muted: 'neutral.600',
    subtle: 'neutral.600',
    inverse: 'paper.white',
    link: 'primary.600',
  },

  // Tier 3 — roles. Every one of these is painted by a component.
  roles: {
    primary: 'primary.600',
    primaryHover: 'primary.700',
    onPrimary: 'paper.white',
    primaryTint: 'primary.50',
    border: 'neutral.300',
    borderStrong: 'neutral.400',
    borderControl: 'neutral.500',
    focus: 'primary.500',
    focusOnInverse: 'primary.300',
    selectedBg: 'primary.50',
    selectedInk: 'primary.700',
  },

  // Tier 4 — signal families, then the five slots they drive.
  signals: {
    families: {
      neutral: { bg: 'neutral.300', ink: 'neutral.900', line: 'neutral.500', solid: 'neutral.600', onSolid: 'paper.white' },
      info: { bg: 'primary.100', ink: 'primary.800', line: 'primary.500', solid: 'primary.600', onSolid: 'paper.white' },
      success: { bg: 'success.100', ink: 'success.800', line: 'success.600', solid: 'success.700', onSolid: 'paper.white' },
      warning: { bg: 'warning.100', ink: 'warning.800', line: 'warning.600', solid: 'warning.700', onSolid: 'paper.white' },
      danger: { bg: 'danger.100', ink: 'danger.800', line: 'danger.500', solid: 'danger.600', onSolid: 'paper.white' },
    },
    slots: {
      default: 'neutral',
      primary: 'info',
      success: 'success',
      warning: 'warning',
      error: 'danger',
    },
  },
});

几何是可选和默认的—— font, (中文(简体) ), radius, (中文(简体) ), control, (中文(简体) ), motion编辑 默认为 @basenative/components' 自己的价值, 主张字节相同 这个包的测试, 所以上游的变化 失败了建设,而不是悄悄地 复置六所地业. 田间应用提升了水龙头目标:

control: { tapMin: '2.75rem' },   // 44px — WCAG 2.2 SC 2.5.5, for gloves

3. 为它服务

import { toCss } from '@basenative/theme';
import { minifyCss } from '@basenative/theme/css';
import { theme } from './theme.js';

// Emits @layer tokens { … } plus the CTI+ bridge. Comments carry the
// rationale for whoever opens the file; strip them at the point of serving.
export const THEME_CSS = minifyCss(toCss(theme));

装入顺序, 匹配已声明的层顺序 (单位:千美元)@layer reset, tokens, layout, components, states:

<link rel="stylesheet" href="/@basenative/components/css">
<style>/* THEME_CSS */</style>

toCss 默认附加桥。 如果你宁愿把它连起来, toCss(theme, { bridge: false }) 加号 @basenative/theme/bridge.css。 。 。 .

4. 打开门

一行,在您自己的测试文件中:

import { test } from 'node:test';
import { assertThemeContrast } from '@basenative/theme/audit';
import { theme } from '../src/theme.js';

test('every shipped colour pairing clears WCAG AA', () => {
  assertThemeContrast(theme);
});

assertThemeContrast 从主题本身的形状写出配对表 — 55 启动器的行——测量真实的相对亮度,并投掷列表 每个失败的行及其实际比率。 它运行一次每个方案 您的主题 因此,一个双重主题要测量两次.

添加此软件包所无法知道的任何内容的行 :

import { assertPairings, defaultPairings, AA_NONTEXT } from '@basenative/theme/audit';
import { toCss } from '@basenative/theme';

assertPairings(toCss(theme, { only: 'light', bridge: false }), [
  ...defaultPairings(theme),
  ['logo on the header band', '--color-border-theme-signal-info', '--color-bkg-theme-surface-inverse', AA_NONTEXT],
]);

CI会失败的

失败被丢弃这是什么意思
scheme is "undefined"defineTheme你没有承诺 光明,黑暗或双重。 见下文——这个不是可选的.
theme.roles is missingdefineTheme跳过了一层 这四者皆有需要.
theme.ink.link references "brand.600", which the theme does not declaredefineTheme细细的参考文献.
ramps.primary.600 is "var(--x)" — a ramp step must be a literaldefineTheme永不以颜色终止的链条 .
theme.signals.slots.warning is unassigneddefineThemerenderBadge({ variant: 'warning' }) 没有颜色.
muted text on sunk surface: … is 4.40:1, below 4.5:1assertThemeContrast一个真正的WCAG失手, 与真实的数字.
既不是符号名称 CTI_BRIDGED 也没有 UPSTREAM_OWNED这个包是自己的 bridge.test.js@basenative/components 发展出可主题符号 桥没有赶上.

一揽子计划背后的四项决定

为什么没有默认主题

出口中的参考主题成为了de-facto org 外观,即 正好相反的一点。 所以没有 上面的启动器是 文档而非进口: 伪造是作者行为。 这是保存的 老实说 starter-guide.test.js,此文件的哪个解析,运行 defineTheme 编辑 块,并声称 neutral 财务报告和审定财务报表 primary 斜坡的字节与 @basenative/components/src/tokens-legacy.css。文件无法从代码中漂移 启动器不能悄悄地变成品牌.

颜色从不默认 。 几何学是——半径阶梯是ergonomics,不是 身份证明.

信号家庭对五个插槽

@basenative/components 宣布五个警徽/警示变式: default | primary | success | warning | error。真正的域没有五个 状态叫这个。 格林普特队是 idle | move | good | dead; 仓库 应用可能是 receiving | staged | short | damaged。 。 。 .

所以主题宣布 家庭名下的家庭,则 指定 改为 五个插槽中每个插槽:

signals: {
  families: {
    idle: { … }, move: { … }, good: { … }, dead: { … },
  },
  slots: { default: 'idle', primary: 'idle', success: 'good', warning: 'move', error: 'dead' },
}

家庭不限于五个,两个名额可以共用一个名额. 每个家庭都有 其本身 --color-*-theme-signal-<name> 用于您自己的 CSS 的属性,和 - 这是 关键部分—— 无论是否是,每个家庭都有对照行 分配给一个槽。无法添加一个从来就没有过的状态颜色 测量.

每个家庭有五个渠道: bg (警徽坐着), ink (中文(简体) ) {\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}那条线 财务报告和审定财务报表 在页面上, line (家族统治,为SC3:1门) 1.4.11,(a), solid (已填入的文本)和 onSolid (文字在填报上).

写这个时测量: 白色 --palette-success-600 (# 16a34a) 为 3:30:1和开通 --palette-warning-600 (#d97706)为3.19:1,而 --palette-error-600 为 4.83: 1. 上游只画过错误的文字 填充,所以它载运 没有破碎的—— 只是一个绿色的"证明"按钮 -600 会的 启动器的成功和警告固体 一步更暗.

黑暗主题:可选,但承诺不是

validateTheme 已经 没有 需要一个深层。 这需要 scheme,则它 实值 'light' | 'dark' | 'dual' 没有默认.

单一策略主题出自 一个街区,没有条件 没有媒体 查询,无 data-theme 变相. 这就是失败的整件事 软件包建于: 待定business.com是一个从未设置过的黑暗站点 data-theme,因此,一个读取器,其OS在光模式下坠入 @media (prefers-color-scheme: dark) 并获得了 --color-bkg-error: #fef2f2 a 包括: 一个近黑页面上的近白警报.

与 scheme: 'dark' 没有什么可掉下来的。 黑暗的价值观是 在每一个方面, color-scheme: dark 被钉住 data-theme 属性停止为负载。 运送这个虫子现在需要申报 scheme: 'dual' 并故意写出一个灯光调色板——此时 控制它.

双重主题镜像 上游的激活模式 在匹配的特异性,所以它的 自己的深色调色板获胜,和 validateTheme 拒绝半主题暗块 他们的家族名单和光线不同.

为什么桥住在这里

components 拥有组件。 内容 --bn-* 阶梯 (tokens-legacy.css)为 已经是一个完全中立的默认, 它应该保持无知的主题 已存在。 成本——这个包裹 名称列表可能落在组件发布后- 是 测量,未假定数字 :

  • CTI_BRIDGED 是桥出( 今天有93个名字) .
  • UPSTREAM_OWNED 名单是故意留下的 每一个都有它的理由.
  • bridge.test.js 读取每个样式表 packages/components/src 失败 如果一个 var(--*) 两个列表中都没有显示.

添加可主题符号的组件发布会打破此软件包的构建 同样的CI运行, 而不是默默无声地去 不主题在六个属性. validateBridge(css, { exclude }) 被导出, 这样消费者就可以运行相同的 检查它安装的任何版本; exclude 携带 组件级和关键词符号名称,从符号源计算,由 文件( 编号tokens/components/*.json 加号 tokens/keyword.json页:1 关于消费者的组件版本 而不是这个包.

这座桥留下了什么 类型尺度、字体权重、行高, 空间阶梯、阴影、松动曲线、z指数和每英寸几何 no. 这些是应用决定,不是调色板决定——在你自己身上压倒它们 CSS (英语). 颜色、 字体家族、 半径阶梯、 控制高度、 水龙头目标 三个运动持续时间都是桥接的.

如何是桥取胜. 生成的符号表在 components 激活 其暗值为 :root:not([data-theme="light"]) 在媒体查询和 时间 :root[data-theme="dark"] ——特异性(0,2,0). 在平原上的一座桥 :root (0,1,0)会输给两者,这就是上游的暗值如何通过 主题。 双桥选择器为 (0,2,1):

html:root[data-theme],
html:root:not([data-theme="light"]) { … }

二者之间,它们都符合 data-theme,包括其缺席, 他们从上游打来 该桥也是UNLAYERED,目的: 上游宣布其核心符号未被分层,层层声明会丢失 任何未分层的 任何特异性 —— 刻度符号是不分层的,所以 内接桥 @layer tokens 永远无法重指它。 没有分开 用于中和的暗名设置: 暗是在同一个同位符上携带的一种模式, 所以 在主题和主题上重新指定一个名称 决定黑暗的意义.


图标

不是图标库——使一行图标读作一集的学科.

import { defineIconSet, renderIcon } from '@basenative/theme/icons';

export const icons = defineIconSet({
  grid: 24,
  strokeWidth: 1.75,
  icons: {
    activity: '<path d="M3 12h4l3 7 4-14 3 7h4"/>',
    check: '<path d="m4.5 12.5 5 5 10-11"/>',
  },
});

renderIcon(icons, 'activity');                      // aria-hidden — it sits beside a label
renderIcon(icons, 'check', { title: 'Verified' });  // role="img" — it IS the label

defineIconSet 抛出任何打破 设定的均衡:一个填充,一个 被烤入中风的颜色或字形六分肌,每英寸 stroke-width中线 样式, 嵌入式 <svg>本身 viewBox一个网格,一个重量, currentColor,默认为装饰.

一个身体是 目录 一、导 言 <svg>这样 width 财务报告和审定财务报表 height 内在的 普通几何并允许—— <rect x="3" y="5" width="18" height="16" rx="2"/> 是日历。 自我放大是外在元素的一个属性 renderIcon 写作和身体不能有.

对比度, 直接

import { contrastRatio, relativeLuminance, AA_TEXT } from '@basenative/theme/contrast';

contrastRatio('#166534', '#dcfce7');  // 6.49

parseCustomProperties(css) 财务报告和审定财务报表 resolveColor(name, props) 碎片是 审计工具的构建来自: var() 链到文字 #rrggbb 并投出一个细长的参考物,一个循环,或者一个非文学终端.

parseCustomProperties 将样式表读作平面图,从而测量双向 主题意味着一次发布一个方案—— toCss(theme, { only: 'dark' })。 。 。 . assertThemeContrast 这样做是为了你.

线性

对比引擎,对接绳, minifyCss 和各种图标 证明在 libs/shared/design 在绿地竞技场, 配对 表格已硬化并连接到 --gp-* 名称空间。 这里的桌子是 参数,每个符号名称都是参数,格林普特的调色板,它的四 承包商的信号家庭,它的网点和标记 留下来,在那里他们 属于.

许可证

阿帕奇 - 2.0

版本与发布记录

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

版本发布日期距今
3.3.0latest2026/11/035 天前
3.2.02026/11/035 天前
3.1.02026/11/017 天前
3.0.02026/10/317 天前
2.4.02026/10/2613 天前
2.3.22026/10/2217 天前

关于 @basenative/theme 的常见问题

@basenative/theme 是什么?

@basenative/components背后的主题层——一个四层令牌形状,一个构建失败的WCAG对比控制,一个单网格图标规则和CTI+桥

如何安装 @basenative/theme?

在项目目录运行 npm install @basenative/theme,或使用 pnpm add / yarn add / bun add。

@basenative/theme 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@basenative/theme 支持 ESM 还是 CommonJS?

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

@basenative/theme 可以商用吗?

@basenative/theme 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@basenative/theme 的下载量有多少?

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

@basenative/theme 还在维护吗?

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