无头组件系统 · 新资源

Ariakit 设计系统.

Ariakit 提供无样式的 React 组件,具有明确的共享状态和可组合的无障碍部件。其组合框将输入的文本与提交的选择分离,使其对搜索驱动的选择器很有用;下面的参考查找器通过分组和不可用结果演示了这种区别。

OpenDesign 指南9 个章节29 个参考条目原始应用指南
01

Ariakit 设计系统界面预览

过滤分组的参考,使用向下/回车选择,检查结果并将其添加到本地阅读列表。

无障碍参考查找器打开完整示例
原生 @ariakit/react 0.4.40 和 React 19。所有视觉令牌和示例参考内容均为原创。· 实现参考
02

Ariakit 设计系统参考指南

Ariakit React 0.4.40 是一款未设样式的无障碍工具包。它不会将下方的页面配色、字体比例或间距作为原生设计主题发布。所有 --app-* 值均为本可运行示例中明确配置的原创内容;组件名称和状态属性均为 Ariakit API。

原创语义配色配置

来源
名称 / 规则值 / 配置用途
--app-canvas#f5f3ee 页面背景。
--app-surface#fff组合框弹出层和参考卡片。
--app-text#292e29 主要正文。
--app-muted#657064 次要标签和提示。
--app-border#d8ddd4 输入框和卡片边界。
--app-action#285f48 主要操作和焦点环。
--app-on-action#fff主要操作上的文字。
--app-active#e3eee4 活动选项和已选操作的界面表面。

原创形状与间距

来源
名称 / 规则值 / 配置用途
--app-radius10px输入框、弹出层和参考卡片的圆角。
--app-space-18px声明的原创间距比例。
--app-space-216px声明的原创间距比例。
--app-space-324px声明的原创间距比例。
--app-space-432px声明的原创间距比例。
Combobox padding13px 48px 13px 14px为展开控件留出空间。
Popover maximum height340px较长结果列表的局部滚动。
Popover gutter8px实际用于分隔输入框与建议项的 gutter 属性。

原创字体排印

来源
名称 / 规则值 / 配置用途
Font familysystem-ui, sans-serif无需下载字体,也不需要 Ariakit 专用字体。
Page heading36px / 1.1; 30px mobile原创页面层级。
Input text15px原创组合框文字大小。
Option text14px原创结果标签大小。
Group label11px, uppercase, 700原创可见分组样式。
Supporting hint12px / 1.7键盘操作说明的节奏。

原生组合与状态

来源
名称 / 规则值 / 配置用途
ComboboxProvider.inputValueControlled query string当前 0.4.40 输入状态为 API;setInputValue 会更新筛选结果。
ComboboxProvider.selectedValueControlled reference name已确认的选择独立于查询内容。
ComboboxGroup / ComboboxGroupLabelFoundations / Components原生带标签的选项分组。
ComboboxPopover.sameWidthtrue与锚点宽度保持一致。
ComboboxItem.disabledtrue for Experimental grid无法选择不可用的记录。
[data-active-item]CSS state selector单独设置键盘活动选项的样式,不与选中状态混淆。
Combobox autoSelectNot enabled in this specimen按下方向键可进入列表,再按 Enter 选择选项。
03

Ariakit 设计系统完整指南

实用指南,介绍如何为真实界面选择、应用和评审此系统。

DESIGN-ariakit.md

完整指导、实现起点和源代码链接。

下载指南

Ariakit 设计系统:从组合框状态模型入手

从组合框状态模型开始

Ariakit 的 ComboboxProvider 会连接标签、可编辑输入框、展开控件、弹出层和选项。Provider 管理它们共享的交互状态,各组件则负责提供结构中的相应部分。这样,搜索框和所选项目详情面板可以独立排布,也不会把每次按键都当作一次新选择。

摘自本 OpenDesign 指南

此参考查找器展示了两个命名分组中的六条记录。筛选由应用程序的数据集合负责;导航和选项选择则由原生组合框负责。替换静态记录并接入其他数据源时,明确划分这一职责边界很有用:筛选后的结果集与当前已确认的项目不一定相同。

阅读配套规范

Ariakit 设计系统:搜索文本与已确认的选择是不同状态

搜索文本和提交的选择是不同的状态

示例使用 inputValue 和 setInputValue 管理正在输入的文本,并使用 selectedValue 和 setSelectedValue 管理已选参考项。在经验证的 0.4.40 版 API 中,inputValue 是当前输入状态的名称;较早的示例可能使用已弃用的 value/setValue 别名。完整代码采用当前名称。

输入“颜色”会缩小建议范围,但不会立即替换显示的详情卡片。选择“色彩角色”后,该项目才会被确认并更新面板。清除搜索内容也会保留上一次选择的参考项。如果把每个未完成的查询都当作已确认值,详情视图就可能跳转到无效或不存在的记录。

阅读配套规范

Ariakit 设计系统:连接标签、输入框、展开控件和弹出层

连接标签、输入、展开器和弹出窗口

ComboboxProvider 会为 ComboboxLabel、Combobox、ComboboxDisclosure 和 ComboboxPopover 提供共享状态。可见标签用于标识输入框。展开控件是一个独立按钮,具有明确的无障碍名称;弹出层则使用 sameWidth 和八像素的 gutter 与锚点保持对齐。

每条结果都是具有稳定 value 的 ComboboxItem。基础和组件是原生的 ComboboxGroup 分区,并带有 ComboboxGroupLabel 标题。在无结构的列表中插入粗体标题,无法表达相同的分组关系;原生组件结构则同时承载这一关系及其键盘交互模式。

阅读配套规范

Ariakit 设计系统:先筛选数据,再渲染原生选项

在渲染原生项目之前过滤数据

示例集合包含五条可用参考项,以及一条刻意设为不可用的“实验性网格”。筛选由应用程序负责,对每条参考项的名称进行不区分大小写的子字符串匹配。只有包含匹配记录的分组才会渲染,从而避免结果之间出现空标题。

查询没有匹配结果时,会显示一条专门的提示,建议清除搜索内容或尝试搜索“颜色”。这条提示不会被设为可选项。相关的不可用记录仍会显示,并带有“不可用”标签;原生 disabled 属性则会阻止它成为已确认的选择。

阅读配套规范

Ariakit 设计系统:验证真实的“下方向键再按 Enter”操作流程

验证实际的向下和回车路径

此示例不启用 autoSelect。输入查询后,按下方向键进入建议列表,再按 Enter 选择活动项。按 Escape 可关闭弹出层。这与可见说明一致,并且已通过检查所选详情的标题来验证,而不只是确认下拉菜单是否出现。

在其他产品中,自动高亮可能更合适,但它会改变方向键导航的起点。如果只有一条结果且它已处于活动状态,再按一次下方向键就可能让焦点回到输入框。此示例避免了这种不一致,并通过原生 data-active-item 属性设置活动项样式,让键盘所在位置与阅读清单中的已保存状态彼此区分。

  1. 输入“颜色”,等待匹配的选项出现。
  2. 按下方向键,再按 Enter,然后检查“色彩角色”详情面板。
  3. 搜索不可用或不存在的参考项,并确认不会误将其设为已选项。

阅读配套规范

Ariakit 设计系统:活动选项、焦点环和已保存状态应使用不同的视觉提示

活动选项、焦点环和保存状态有不同的提示

输入框和展开控件使用清晰可见的焦点轮廓。活动选项会显示浅绿色背景,并通过原生 data-active-item 属性添加一像素的内侧轮廓。不可用选项会降低视觉强调,同时保留明确的文字标签。这些样式不会仅依赖悬停状态。

所选参考项已在阅读列表中时,“添加”按钮会变为禁用状态并更改文案。已保存状态属于应用,与组合框当前激活的结果相互独立。使用浅绿色的已保存操作界面表面,可避免让禁用操作看起来像另一个可用的主按钮。

阅读配套规范

Ariakit 设计系统:阅读列表不会取代当前参考项

阅读列表不会替换当前参考

添加参考项时,只会将其名称追加到本地数组一次;当前参考项仍会显示,方便用户查看其备注。阅读列表的每一行都有明确命名的“移除”按钮。移除条目后,如果该参考项仍处于选中状态,“添加”按钮会重新启用。条目数量由数组长度计算得出。

示例详情内容提供了有关排版、间距和组件的指导,让用户可以实际探索查找器。所有值都明确标注为原创示例内容。读者不应将 Type scale 记录中的“Display 36px”误认为 Ariakit 的排版令牌;Ariakit 并未提供这套设计语言。

阅读配套规范

Ariakit 设计系统:弹出层需要空间,但不应改变整页尺寸

弹出层需要空间,但不应改变整页尺寸

弹出层宽度与输入框一致,并将自身高度限制在 340px。结果较多时,会在此范围内滚动,而选中详情卡片仍作为普通文档内容显示。即使详情卡片移至另一列,也要让输入框、展开控件和选项弹出层保持关联;确认选项后,仍应更新同一条选中记录。

在原有的 700px 断点下,查找器与详情卡片会纵向堆叠。搜索控件、键盘提示和阅读列表在文档顺序中仍位于选中卡片之前。装饰性的参考图案对辅助技术隐藏,因为真正传达含义的是标题和备注。

  • 限制较长选项列表的局部高度,同时保持条目间距易于阅读。
  • 将已确认的详情面板放在临时弹出层之外。
  • 在移动设备上保留可见的输入框标签和键盘提示。

阅读配套规范

Ariakit 设计系统:连接 Provider、Collection 和样式表

连接 Provider、Collection 和样式表

在 React 项目中安装 @ariakit/react 0.4.40,并保留 App.jsx 中完整的 Provider/组件组合。当前示例将 inputValue/setInputValue 和 selectedValue/setSelectedValue 分别作为两组受控状态。即使当前输入查询将选中记录筛除出弹出层,也要保留该记录。

将提供的 style.css 与组件一同导入。其中的 data-active-item 规则对应组合框的键盘焦点位置,而已保存列表的样式则对应应用状态。替换为异步加载的结果时,请保留这两条状态通道以及分组/选项之间的关系;不要用临时查询字符串替代已确认的详情面板。

阅读配套规范

Ariakit 设计系统组件示例

完整的受控参考项查找器

Provider、标签、输入框、展开控件、分组弹出层项目和本地阅读列表操作。

jsx
import React,{useState} from 'react';
import * as Ariakit from '@ariakit/react';
const resources=[{name:'Type scale',group:'Foundations',code:'Aa',description:'A compact editorial hierarchy for headings, supporting text and labels.',notes:['Display · 36px / 1.1','Section · 24px / 1.25','Body · 16px / 1.6'],color:'#f0dbc3'},{name:'Color roles',group:'Foundations',code:'◐',description:'Separate surface, text, boundary and action roles before naming brand colors.',notes:['Canvas and raised surface','Primary and muted text','Action and on-action pair'],color:'#d6e5d8'},{name:'Spacing rhythm',group:'Foundations',code:'▥',description:'Use a small set of intervals to show relationships between content groups.',notes:['8px within a compact group','16px between related controls','32px between sections'],color:'#d6dfee'},{name:'Form field',group:'Components',code:'⌑',description:'Keep the label, hint, input and error message in one understandable group.',notes:['Persistent visible label','Hint before an error exists','Error connected to the input'],color:'#e9d8de'},{name:'Navigation menu',group:'Components',code:'☷',description:'A small set of destinations with a visible current location and keyboard access.',notes:['Destination labels describe content','Current item has a text/state cue','Collapsed layout keeps names'],color:'#e7e0c5'},{name:'Experimental grid',group:'Components',code:'▦',description:'Unavailable in this sample collection.',notes:[],color:'#e3e3e3',disabled:true}];
export default function App(){const[query,setQuery]=useState(''),[chosen,setChosen]=useState('Type scale'),[saved,setSaved]=useState([]);const selected=resources.find(x=>x.name===chosen)||resources[0],matches=resources.filter(x=>x.name.toLowerCase().includes(query.toLowerCase()));return <main className="resource-finder"><header><p className="eyebrow">ARIAKIT · ACCESSIBLE PRIMITIVES</p><h1>A reference, within reach.</h1><p>Search a small collection with the keyboard, inspect the result and keep a local reading list.</p></header><div className="finder-grid"><section className="search-area"><Ariakit.ComboboxProvider inputValue={query} setInputValue={setQuery} selectedValue={chosen} setSelectedValue={setChosen}><Ariakit.ComboboxLabel className="label">Find a reference</Ariakit.ComboboxLabel><div className="input-row"><Ariakit.Combobox className="combobox" placeholder="Try type, color or form" /><Ariakit.ComboboxDisclosure className="disclosure" aria-label="Show reference suggestions">⌄</Ariakit.ComboboxDisclosure></div><Ariakit.ComboboxPopover gutter={8} sameWidth className="popover">{['Foundations','Components'].map(group=>{const items=matches.filter(x=>x.group===group);return items.length?<Ariakit.ComboboxGroup key={group}><Ariakit.ComboboxGroupLabel className="group-label">{group}</Ariakit.ComboboxGroupLabel>{items.map(x=><Ariakit.ComboboxItem key={x.name} value={x.name} disabled={x.disabled} className="combo-item"><span>{x.name}</span><small>{x.disabled?'Unavailable':x.group==='Foundations'?'Guide':'Pattern'}</small></Ariakit.ComboboxItem>)}</Ariakit.ComboboxGroup>:null})}{!matches.length&&<p className="empty" role="status">No matching reference. Try “color” or clear the search.</p>}</Ariakit.ComboboxPopover></Ariakit.ComboboxProvider><p className="hint">Type to filter. Use ↓ and ↑ to browse, Enter to choose, Escape to close.</p><div className="collection-note"><span>06</span><div><strong>Sample references</strong><p>Five available, one deliberately disabled to show an unavailable item.</p></div></div><section className="reading-list"><h2>Your reading list <small>{saved.length}</small></h2>{saved.length?<ul>{saved.map(name=><li key={name}><span>{name}</span><Ariakit.Button className="remove" onClick={()=>setSaved(saved.filter(x=>x!==name))} aria-label={`Remove ${name}`}>Remove</Ariakit.Button></li>)}</ul>:<p className="muted">Choose a reference, then add it here.</p>}<p className="local-note">This list stays in memory and clears on reload.</p></section></section><section className="reference-card" aria-label="Selected reference"><div className="reference-art" style={{background:selected.color}} aria-hidden="true"><span>{selected.code}</span><i></i></div><div className="reference-body"><p className="eyebrow">{selected.group} · SAMPLE CONTENT</p><h2>{selected.name}</h2><p>{selected.description}</p><ul className="note-list">{selected.notes.map(note=><li key={note}>{note}</li>)}</ul><Ariakit.Button className="primary" disabled={saved.includes(selected.name)} onClick={()=>setSaved([...saved,selected.name])}>{saved.includes(selected.name)?'Added to reading list':'Add to reading list'}</Ariakit.Button><p className="scope-note">These sample reference values are original content, not Ariakit design tokens.</p></div></section></div><p className="selection-status" role="status">Selected reference: {selected.name}</p></main>}

代码参考

  • inputValue 是搜索字符串;selectedValue 是已确认的参考项。
  • 符合查询条件的禁用条目仍会显示。
  • 原始示例中的排版和间距值并非 Ariakit 的默认值。

完整的原创视觉配置

语义化应用变量、焦点环、激活选项样式和响应式布局。

css
:root{--app-canvas:#f5f3ee;--app-surface:#fff;--app-text:#292e29;--app-muted:#657064;--app-border:#d8ddd4;--app-action:#285f48;--app-on-action:#fff;--app-active:#e3eee4;--app-radius:10px;--app-space-1:8px;--app-space-2:16px;--app-space-3:24px;--app-space-4:32px;font-family:system-ui,sans-serif}*{box-sizing:border-box}body{margin:0;background:var(--app-canvas);color:var(--app-text)}button,input{font:inherit}.resource-finder{max-width:1080px;padding:32px;margin:auto}.eyebrow{font-size:11px;font-weight:700;letter-spacing:.13em;color:var(--app-muted)}h1{font-size:36px;line-height:1.1;margin:16px 0}header>p:last-child{font-size:15px;line-height:1.7;max-width:620px;color:var(--app-muted)}.finder-grid{display:grid;grid-template-columns:1fr 1.1fr;gap:40px;margin-top:32px}.label{display:block;font-size:14px;font-weight:650;margin-bottom:10px}.input-row{position:relative;display:flex}.combobox{width:100%;padding:13px 48px 13px 14px;border:1px solid var(--app-border);border-radius:var(--app-radius);background:var(--app-surface);color:var(--app-text);font-size:15px}.disclosure{position:absolute;right:4px;top:4px;bottom:4px;width:36px;border:0;background:transparent;border-radius:6px;cursor:pointer}.combobox:focus,.disclosure:focus-visible,button:focus-visible{outline:3px solid var(--app-action);outline-offset:3px}.popover{z-index:50;max-height:340px;overflow:auto;background:var(--app-surface);border:1px solid var(--app-border);border-radius:var(--app-radius);box-shadow:0 8px 28px #243c2420;padding:8px}.group-label{font-size:11px;letter-spacing:.06em;text-transform:uppercase;font-weight:700;color:var(--app-muted);padding:10px}.combo-item{display:flex;justify-content:space-between;gap:16px;align-items:center;padding:12px 10px;border-radius:6px;cursor:default;font-size:14px;scroll-margin:8px}.combo-item small{font-size:11px;color:var(--app-muted)}.combo-item[data-active-item]{background:var(--app-active);outline:1px solid var(--app-action);outline-offset:-1px}.combo-item[aria-disabled=true]{opacity:.45}.empty{padding:12px;font-size:14px;line-height:1.6}.hint{font-size:12px;line-height:1.7;color:var(--app-muted);margin-top:12px}.collection-note{display:flex;gap:16px;align-items:flex-start;border-block:1px solid var(--app-border);padding:24px 0;margin:28px 0}.collection-note>span{font-size:40px;line-height:1;color:var(--app-action)}.collection-note strong{font-size:14px}.collection-note p{font-size:13px;line-height:1.6;color:var(--app-muted);margin:8px 0 0}.reading-list h2{font-size:17px;display:flex;justify-content:space-between}.reading-list h2 small{background:var(--app-active);border-radius:30px;padding:3px 8px}.reading-list ul{padding:0;list-style:none}.reading-list li{display:flex;justify-content:space-between;gap:12px;padding:12px 0;border-bottom:1px solid var(--app-border);font-size:14px}.remove{background:none;border:0;text-decoration:underline;color:var(--app-action);font-size:12px;cursor:pointer}.muted,.local-note,.scope-note{color:var(--app-muted);font-size:12px;line-height:1.7}.reference-card{border:1px solid var(--app-border);border-radius:var(--app-radius);overflow:hidden;background:var(--app-surface)}.reference-art{height:160px;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden}.reference-art span{font-size:96px;font-family:Georgia,serif;line-height:1;position:relative;z-index:1}.reference-art i{position:absolute;width:190px;height:190px;border:1px solid currentColor;opacity:.2;border-radius:50%;left:50%;top:35%}.reference-body{padding:24px}.reference-body h2{font-size:28px;margin:12px 0}.reference-body>p{line-height:1.65;font-size:14px}.note-list{list-style:none;padding:0;margin:24px 0}.note-list li{padding:10px 0;border-bottom:1px solid var(--app-border);font-size:13px}.primary{padding:12px 18px;border:1px solid var(--app-action);background:var(--app-action);color:var(--app-on-action);border-radius:6px;cursor:pointer;font-weight:600;font-size:14px}.primary:disabled{background:var(--app-active);color:var(--app-action);cursor:default}.scope-note{margin-top:16px}.selection-status{font-size:12px;color:var(--app-muted);margin-top:24px}@media(max-width:700px){.resource-finder{padding:24px 16px}h1{font-size:30px}.finder-grid{grid-template-columns:1fr;gap:28px}.reference-art{height:135px}}

代码参考

  • Ariakit 提供交互行为和语义,不提供这套配色或间距系统。
  • 激活结果通过 data-active-item 设置样式。
  • 弹出层拥有独立的最大高度和滚动区域。
04

Ariakit 设计系统 AI Agent 设置

实现起点

已在 @ariakit/react 0.4.40 和 React 19 环境下验证。使用当前的 inputValue/setInputValue 名称和受控的 selectedValue。

jsx
// Create the project first:
// npm create vite@latest ariakit-specimen -- --template react
// cd ariakit-specimen && npm install
// In a Vite React project:
// npm install @ariakit/[email protected] react@19 react-dom@19
// Save the complete component as App.jsx and the visual configuration as style.css.
import React from 'react';
import {createRoot} from 'react-dom/client';
import App from './App.jsx';
import './style.css';
createRoot(document.getElementById('root')).render(<App />);
// index.html contains <div id="root"></div>.
// No Ariakit theme stylesheet is required; style.css is this example's original theme.
// Keep the template index.html and replace its entry with the code above.
// npm run dev

适用于你项目的提示词

Agent 提示词
在 React 中使用提供的 Ariakit 0.4.40 参考项查找器。确保 ComboboxProvider 的 inputValue/setInputValue 与 selectedValue/setSelectedValue 相互独立;保留原生标签分组和禁用的 Experimental 网格选项,并在渲染条目之前筛选数据。关闭 autoSelect 时,保留已验证的“按向下键再按 Enter”操作路径,并将 data-active-item 的样式与已保存状态分别处理。将已确认的参考项加入本地阅读列表,避免重复,并允许移除条目而不丢弃当前详情。包含完整的原始样式表。
05

Ariakit 设计系统的来源与范围

Ariakit React 0.4.40。所有视觉令牌、图案和示例参考项值均为原创配置;此处未声称使用任何官方 Ariakit 配色或排版比例。阅读列表状态保存在本地。