ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

opencode console 的 CSS 架构:基于 data-page / data-component / data-slot 三级选择器的样式组织实践

opencode console 的 CSS 架构:基于 data-page / data-component / data-slot 三级选择器的样式组织实践 opencode console 的 CSS 架构:基于 contenteditable="false">【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode本文以 opencode 仓库中 console 应用的 opencode Agent 配置文件 css.md 为核心,系统讲解该团队约定的一套数据属性驱动的 CSS 架构:如何用data-page→data-component→data-slot三级层级组织选择器、如何用 data 属性表达组件状态、以及样式文件在src/style/与路由目录中的落位规则,并结合 src/style 与首页 index.css 的真实源码,验证这套规范在仓库中的实际落地方式。读完本文,你将掌握:一套不依赖类名命名(如 BEM/Tailwind)的 CSS 组织方法、CSS 结构与 JSX 结构解耦的设计动机,以及如何在自己的 Solid/React 项目中复刻 token 分层、暗色模式与页面级样式就近放置的工程实践。一、规范的载体:一个 opencode Agent 文件在 opencode 仓库中,AI Agent 的行为可以通过.opencode/agent/目录下的 Markdown 文件来定义。css.md 就位于 console 应用的.opencode/agent/目录下,其 frontmatter 声明了触发条件:--- description: use whenever you are styling a ui with css ---这意味着:当 Agent 被要求给 UI 写样式时,opencode 会自动加载这份提示,约束其产出的 CSS 风格。文件本体以第二人称指令的形式,规定了编写干净、可维护、使用现代技术的 CSS 的完整规则集。它不是一份给人读的设计文档,而是一份把团队 CSS 约定固化进 AI 协作流程的可执行规范——这也是本文值得展开的背景:规范的价值不仅在于人遵守,还在于 Agent 能稳定遵守。二、核心模型:data-page →>[data-pagehome] { [data-componentheader] { [data-slotlogo] { } } }规则可以浓缩为三条:顶层页面用data-page作用域化(top level pages are scoped usingdata-page);页面内部用data-component拆分为组件(pages can break down into components usingdata-component);组件内部用data-slot拆分为插槽(components can break down into slots usingdata-slot)。并附带两条硬性纪律:很少需要把组件嵌套在另一个组件里写(should rarely need to nest components inside other components);绝不把组件嵌套进插槽,绝不把插槽嵌套进其他插槽(NEVER nest components inside slots / slots inside slots)。这个层级是选择器特异性(specificity)的自然分级:页面级规则特异性最低,插槽级最高,越靠近元素越精细,覆盖关系可预期。三、关键设计:CSS 层级严格,DOM 层级自由原文档中最强调的一点(原文用加粗 IMPORTANT 标出):层级规则约束的是 CSS 结构,不是 JSX/DOM 结构。CSS 文件中的层级不需要与 DOM 中的嵌套一致——某个组件在 DOM 里包在另一个组件内部,在 CSS 里完全可以平铺在同一层:[data-pagehome] { [data-componentscreenshots] { [data-slotleft] { /* styles */ } [data-slotcontent] { /* styles */ } } [data-componenttitle] { /* can be at same level even though nested in DOM */ } }对应的 JSX 则可以按语义任意嵌套:div>[data-componentmodal] { opacity: 0; [data-stateopen] { opacity: 1; } }[data-stateopen]这种父选择器上追加属性的写法,把默认态 状态切换收拢在同一个组件块里,状态变更只是 JSX 上data-state取值的切换,无需增删 class。这套写法在仓库里能看到完全一致的实现。console 应用的可复用按钮 button.css 就是教科书式的范例:基础样式写在[data-componentbutton]上,变体与状态全部用 data 属性叠加:[data-componentbutton] { display: inline-flex; align-items: center; gap: var(--space-2); padding: var(--space-3) var(--space-4); border-radius: var(--space-2); font-size: var(--font-size-md); /* ... */ [data-colorprimary] { background-color: var(--color-primary); color: var(--color-primary-text); :hover:not(:disabled) { background-color: var(--color-primary-hover); } :active:not(:disabled) { background-color: var(--color-primary-active); } } [data-sizesmall] { padding: var(--space-2) var(--space-3); font-size: var(--font-size-sm); } [data-sizelarge] { padding: var(--space-4) var(--space-6); font-size: var(--font-size-lg); } [data-sloticon] { display: flex; width: 1em; height: 1em; } }注意它精确体现了规范里的每一条:组件级选择器[data-componentbutton]、状态/变体用[data-color]、[data-size]、:disabled表达、内部图标用[data-sloticon]插槽而非元素选择器,且所有数值都引用 token 变量(--space-*、--font-size-*),没有任何魔法数字。五、选择器纪律:不裸写元素选择器规范明确反对 span这类只针对元素类型的选择器:该给这个元素一个 slot 名就给它一个 slot 名;只在语义上确实合理时才允许例外,例如给列表中的li加样式。这保证了选择器始终挂在稳定的 data 属性上,而不是挂在实现细节(某个标签换另一个标签)上——重构 DOM 时样式不会跟着碎掉。六、文件组织:token / component / 页面就近放置规范对文件的落位做了三分法约定:目录职责约束src/style/全局通用样式(universal styling rules)不得包含任何页面专属内容src/style/token/项目全部设计 token(颜色、字体、间距等)集中管理src/style/component/可复用组件样式,如按钮、输入框跨页面共享src/routes/页面专属样式与页面文件同目录、同名放置about.tsx对应about.css并且页面样式必须用data-page页面名作用域化:./src/routes/about.tsx的样式写在./src/routes/about.css,且以data-pageabout圈定。仓库现状与这条规范逐条对应:src/style/index.css 是整个全局样式的唯一入口,import 顺序即优先级语义:token→component→reset→base:import ./token/color.css; import ./token/font.css; import ./token/space.css; import ./component/button.css; import ./reset.css; import ./base.css;token/color.css 是token 集中管理的实证:所有颜色以 CSS 变量挂在:root上(--color-bg、--color-text-secondary、--color-accent、--color-border-muted等),并用media (prefers-color-scheme: dark)整体覆写一套暗色值。组件与页面只需引用变量,主题切换零改动。token/font.css 定义了--font-size-2xs到--font-size-9xl的 14 档字号,以及--font-mono字体栈;token/space.css 定义了--space-0到--space-96的 40 档间距,外加--border-radius-sm/md/lg三档圆角。reset.css 是一份现代化的 reset:box-sizing: border-box全局化、清除默认 margin、媒体元素display: block; max-width: 100%、标题text-wrap: balance、段落text-wrap: pretty、#root建立根层叠上下文,并在prefers-reduced-motion: no-preference下开启interpolate-size: allow-keywords支持关键词尺寸动画。页面就近放置的规则在路由目录里随处可见:routes/index.tsx 与 routes/index.css 同名配对,black.tsx/black.css、user-menu.tsx/user-menu.css、workspace.tsx/workspace.css、[...404].tsx/[...404].css全部遵循页面与样式同目录同名的约定。七、真实页面深读:index.css 如何执行这套规范首页样式 routes/index.css 是这份 Agent 规范最完整的落地样本(全文约 1250 行),其中有几个值得拆解的技法。1. 页面级 token:在data-page作用域内定义本页面专属变量[data-pageopencode] { --color-background: hsl(0, 20%, 99%); --color-background-weak: hsl(0, 8%, 97%); --color-text-strong: hsl(0, 5%, 12%); --color-border-weak: hsla(0, 100%, 3%, 0.12); /* ... */ }页面专属颜色没有硬编码进每条规则,而是先声明为页面级 CSS 变量,再由后代选择器引用。这相当于在全局 token与页面实现之间加了一层页面作用域的 token,既遵守了页面样式用data-page圈定,又保持了暗色模式切换的集中性:[data-pageopencode] { media (prefers-color-scheme: dark) { --color-background: hsl(0, 9%, 7%); --color-text-strong: hsl(0, 15%, 94%); /* ... */ } }2. 响应式布局参数同样变量化[data-pageopencode] { background: var(--color-background); --padding: 5rem; --vertical-padding: 4rem; --heading-font-size: 1.375rem; media (max-width: 60rem) { --padding: 1.5rem; --vertical-padding: 3rem; } display: flex; gap: var(--vertical-padding); flex-direction: column; font-family: var(--font-mono); }断点切换时只改变量值,布局规则不动——这是token 化思想在页面级的延伸。3. 组件级样式严格落在 page 作用域内首页的增长统计模块严格遵循 page → component 的层级:[data-pageopencode] { [data-componentgrowth-stats] { margin-top: 48px; display: flex; gap: 64px !important; media (max-width: 40rem) { display: none !important; } [data-componentgrowth-stat] { display: flex; flex-direction: column; gap: 24px; /* ... */ } } }这里出现了一个文档允许的特例——growth-stats内嵌套了growth-stat组件——正好对应规范中rarely need to nest components inside other components的弹性:父复数、子单数的列表型组件属于少数合理场景。而 DOM 一侧,data-page/data-component/data-slot三种属性在 index.tsx 中大量出现,例如main>[data-slotbr] { display: block; media (max-width: 60rem) { display: none; } }配合 JSX 里的span>!-- Used in multiple places on the same page -- section>[data-pagehome] { /* Reusable title component defined at page level since its used in multiple components */ [data-componenttitle] { text-transform: uppercase; font-weight: 400; } [data-componentinstall] { /* install-specific styles */ } [data-componentscreenshots] { /* screenshots-specific styles */ } }title组件因为样式和行为在整页一致,提升到页面级定义,与install、screenshots平级;若组件只在某个组件内部使用,则留在该组件层定义。跨页面复用的组件(如按钮)则上升为全局,进入src/style/component/——仓库中的 button.css 正是这一层的实例。九、关键澄清(原文 Key Clarifications 全量继承)原文最后给出了四条收束性澄清,适合作为日常写样式时的检查清单:JSX 嵌套是自由的(JSX Nesting is Flexible):组件可以嵌在插槽里,插槽里也可以放组件——以语义合理为准;CSS 层级是严格的(CSS Hierarchy is Strict):CSS 中必须遵循 pages → components → slots 结构;可复用组件定义在共享发生的那一层:全页共享就放页面级,组件内部共享就放组件级,跨页面共享就进src/style/component/;DOM 结构与 CSS 结构不必一致(DOM vs CSS Structure):两者各自为各自的目的优化。十、小结:这套规范解决了什么问题从 css.md 的完整规则集与 src/style、routes/index.css 的源码对照来看,opencode console 的 CSS 架构解决的是三个经典痛点:作用域冲突:没有全局类名,选择器以data-page为天然命名空间,页面间样式互不泄漏,也无需引入 CSS Modules/Scoped 工具链;特异性失控:层级固定为三层,覆盖关系可推理,!important只在响应式收放时点状出现;样式与结构耦合:状态、变体、插槽全部由 data 属性寻址,JSX 重构换标签不影响样式,暗色模式与响应式通过 token 变量集中切换。同时,把这份规范写成.opencode/agent/下的 Agent 文件,让人写的约定成为AI 写样式时的默认行为——在 opencode 这样一个由 AI 深度参与开发的项目里,这本身就是工程实践的一部分:规范不只写在文档里,更写在 Agent 的上下文里。【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表