简体中文
扩展与自定义主题
@astryx-vue/themes 开箱自带了 7 套主题:Neutral(中性默认)、Butter(黄油暖金)、Chocolate(巧克力暖棕)、Gothic(哥特深邃)、Matcha(抹茶淡绿)、Stone(石石板岩)与 Y2K(复古千禧)。
每一套主题本质上都是一个已经写好的 CSS 文件(例如 butter.css)。包内不包含复杂的运行时主题注册或状态管理逻辑,因此运行性能极高、对打包工具完全透明。
场景一:只修改个别变量(最常用的做法)
如果你只想微调某个颜色或把所有圆角稍微改大一点,只需要在你的项目样式里覆盖对应的 CSS 变量即可。
推荐做法: 将覆盖代码写在 @layer astryx.overrides 里面,这样既能稳定覆盖默认主题,又绝对不会破坏组件原有的布局骨架:
css
/* 确保这段样式在引入 @astryx-vue/themes/neutral.css 之后加载 */
@layer astryx.overrides {
:root {
/* 把主文本色调深一点 */
--astryx-color-text-primary: light-dark(#1a1a1a, #f5f5f5);
/* 把全站组件的基础圆角改成 8px */
--astryx-radius-element: 0.5rem;
}
}场景二:在页面局部区域套用计算主题
如果不是全站修改,而是希望某个特定的弹窗、侧边栏或卡片拥有一套独立生成的品牌色,可以使用 Vue 组件 Theme,搭配 defineTheme() 工具函数。color、typography、radius、motion 这几项只要给几个数,就能算出一整套变量:
vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { defineTheme, Theme } from '@astryx-vue/core/Theme'
const brand = defineTheme({
name: 'brand',
// 一个颜色;也可以写 [浅色种子, 深色种子],深浅两套各自从自己的种子算
color: { accent: ['#0064E0', '#48CAE4'] },
typography: { scale: { base: 14, ratio: 1.2 } },
radius: { base: 4, multiplier: 1.5 },
})
</script>
<template>
<!-- 外部维持默认主题 -->
<Button label="默认风格按钮" />
<!-- 局部子树应用新主题 -->
<Theme :theme="brand">
<Button variant="primary" label="品牌风格按钮" />
</Theme>
</template>Theme 会把这组 CSS 变量写到子树的根节点上,并且会重新绑定文字、按钮等别名,防止它们停留在外层已经算好的颜色上。
覆盖主题的变量
Theme 会把变量写进外层元素里面的一个 <style> 里。这条规则的选择器是 :scope.astryx-theme,权重相当于两个 class,而且这个 <style> 在文档里排在你的样式表后面。所以,如果你的覆盖写在 @layer astryx.overrides 里,只用一个或两个 class 去选外层元素,是压不过它的。可以这样选:
- 把覆盖写在没放进任何
@layer的普通样式里。不分层的样式总是赢过分层里的,所以一定能赢。这是最省事的办法。 - 如果一定要写在
@layer astryx.overrides里,可以在外层元素上叠至少 3 个 class(.wrapper.wrapper.wrapper);也可以在Theme外面自己再包一个带 id 的元素,用#brand .wrapper这样的写法去选。Theme不会把id写到它的外层元素上,所以单写#id是选不中的。
适配出来的变量也一样,不管条件有没有成立。直接写在外层元素 style 属性里的值同样会赢。
主题名要唯一。两个 name 相同的 Theme 共用一份样式。如果它们的值不一样,后渲染的那个会对两边都生效。不同的主题请起不同的名字。
场景三:只让某个组件与众不同
用 components 给组件的某个部位换样式
components 里的每个键叫「换肤挂点」(theme target),写法是 组件名-部位,全部小写、用短横线连起来,例如 button、card-header、radio-indicator、progress-bar-fill。每个挂点就是元素上一个固定的 astryx-* 类名。
挂点下面的键用来挑选状态:
base:这个部位本身。variant:primary:variant等于primary的时候,对应元素上的[data-variant="primary"]。checked这种单个词:对应[data-checked="checked"]。- 用
+把两个条件连起来,两个都满足才生效。
ts
const brand = defineTheme({
name: 'brand',
components: {
'button': { 'variant:primary': { borderRadius: '999px' } },
'card-header': { base: { color: 'var(--brand-ink)' } },
},
})几点细节:
:hover只对能点的控件生效,禁用的按钮不会因为鼠标悬停而变色。card、dialog、bottom-sheet、dropdown-menu、context-menu、segmented-control这几个组件,主题里改了borderRadius或padding,里面跟着它走的部位也会一起变,比如卡片头部的圆角。chat的borderRadius和padding会写到输入区的圆角和内边距上,progress-bar-mark的width和height会写到那根刻度线上。
旧写法已经不再支持
上游在 0.7.0 删掉了一批旧名字,我们也跟着删了。驼峰写法(比如 buttonIcon)或者大写开头(比如 Card)会直接报错;连在一起的旧名(比如 progressbar)不会报错,但什么都匹配不到。请改成右边的新名字:
| 已删除 | 请改用 |
|---|---|
base-table | table |
checkbox | checkbox-indicator |
codeblock、codeblock-copy-button、codeblock-header、codeblock-title | code-block、code-block-copy-button、code-block-header、code-block-title |
date-input-clear-icon、date-range-input-clear-icon、multi-selector-clear-icon、selector-clear-icon | input-clear-icon |
hovercard | hover-card |
navicon | nav-icon |
popover-surface | popover |
progressbar、progressbar-fill、progressbar-mark、progressbar-track | progress-bar、progress-bar-fill、progress-bar-mark、progress-bar-track |
radio、radio-dot | radio-indicator、radio-indicator-dot |
statusdot | status-dot |
textarea | text-area |
组件也不再输出 .primary、.sm 这种光秃秃的类名。你自己的 CSS 里如果写了 .astryx-button.primary,请改成 .astryx-button[data-variant="primary"]。
直接覆盖组件变量
如果你只想覆盖单个组件的颜色,也可以直接改它的专属变量。例如 packages/themes/neutral/button.css 里,按钮背景对应的是 --astryx-button-primary-background:
css
@layer astryx.overrides {
.special-container {
--astryx-button-primary-background: light-dark(#4338ca, #6366f1);
}
}进阶:主题自己的变量、环境适配和继承
主题局部变量 localTokens
有些值只有你这一套主题用得上,比如品牌的墨水色。把它放进 localTokens,键就是完整的 CSS 变量名,原样保存,不会加前缀:
ts
defineTheme({
name: 'brand',
localTokens: { '--brand-ink': ['#0A2540', '#E6F0FF'] },
components: { 'card-header': { base: { color: 'var(--brand-ink)' } } },
})不能拿共享的名字(比如 --color-accent)来当局部变量,因为组件已经在读它了。继承这套主题的子主题会一起继承局部变量,也可以改它们的值。局部变量之间不能互相引用成一个圈,否则会报错。
按环境调整 adaptations
有时候只想在某些环境下换一组值,比如「手机这类触屏、而且屏幕比较窄的时候,正文字号放大一点」。adaptations.rules 是一串按顺序排好的规则,每条写成 { when, value }:
ts
defineTheme({
name: 'brand',
typography: { scale: { base: 14, ratio: 1.2 } },
adaptations: {
rules: [{
when: { pointer: 'coarse', width: { below: 'lg' } },
value: { typography: { scale: { base: 16, ratio: 1.1736 } } },
}],
},
})when可以写屏幕宽度范围(用断点名:sm640、md768、lg1024、xl1280、2xl1536,单位都是 CSS 像素,可以用widthBreakpoints改)、pointer(coarse粗指针,也就是手指;fine细指针,也就是鼠标)、contrast(对比度偏好)、motion(是否减弱动效)。同一条when里写的条件要全部满足。value能写的东西和主题本身一样。只写了一半的字号阶梯、圆角或动效,缺的部分会从主题自己的设置里补上。- 规则按顺序在主题之后生效,同时满足好几条时,后面的赢。
- 这些规则只通过 CSS 生效,
useTheme().token()读到的始终是主题本身的值。 - 规则里的变量排在根 token 后面,选择器和根 token 一样,所以条件成立时按层叠顺序盖过根值,没有用
!important。想覆盖它,请看下面的「覆盖主题的变量」。
继承 extends
extends 会把另一套主题算好的东西全部带过来:变量、局部变量、组件样式、onDark 和 onLight、环境规则、图标和指示器。新主题自己写的值优先。继承来的环境规则会按新主题的字号阶梯重新算一遍。
写错的值会被丢掉
如果某个值会提前结束这条 CSS 声明,比如 red; } body { display: none,它会被丢掉,控制台里会有一条警告,主题里其他的值照常生效。看起来奇怪但其实合法的 CSS,比如引号里的分号、data URL,会原样保留。
注意事项
- 不需要另外准备深浅两套样式表: 因为变量本身采用
light-dark()机制,覆盖时也推荐同时写下浅色和深色两个取值。 - 多主题隔离: 预设主题除了 Neutral 会同时挂在
:root上,其他 6 套都是通过属性选择器[data-astryx-theme='...']局部隔离的,多主题并存时互不干扰。关于明暗切换的完整细节,请参阅 深色模式配置。 - Tailwind: 本包不提供 Tailwind 桥接文件。样式本身就是普通的 CSS 变量和
@layer,Tailwind 或 UnoCSS 项目可以直接写bg-[var(--astryx-color-background-surface)],或者在自己的主题配置里做映射。