首页Home / 设计UI
Materin UI v0.1
一套 UI,三个插件,一个网页 —— 颜色、形状、间距、命名都从一个文件里取。 One UI for three plugins and one site — colour, shape, spacing and naming all come from a single file.
三条原则
颜色跟随,形状固定
颜色取宿主的主题变量:在 Obsidian 里换成任何主题,插件跟着变,不需要改代码;在网页里没有宿主变量,就用品牌默认值。圆角和间距反过来固定下来,谁都不许即兴发挥。
只有一个改风格的地方
界面代码里不允许出现裸色值(#1f1f1f 这种),一律引用 --materin-*。改一个文件,三个插件和本站一起变。
组件按名定位
每个组件有唯一的、带用途的名字,并登记在组件清单里。人和 AI 都能按名找到它、按名改它,改样式时不会改错地方、也不会漏。
令牌
取值规则是「先宿主、后品牌」:表中列出的是宿主变量缺失时的回落值。下面的色块直接引用令牌,所以你看到的就是当前主题下的真实取值。
强调色
| 令牌 | 取值 | 实测来源 |
|---|---|---|
--materin-accent | var(--interactive-accent, #5b8cff) | Office / View 用 --color-accent,Ctx 用 --interactive-accent;可点击、可选中应以 --interactive-accent 为准 |
--materin-accent-soft | 强调色 22% 透明 | 由强调色派生,用于底纹与选中态 |
--materin-on-accent | var(--text-on-accent, #ffffff) | 实心按钮上的文字色,别的地方不用白色硬写 |
--materin-secondary | var(--color-cyan, #6ee7d5) | 次要强调:标签、首屏副色 |
表面与文字
| 令牌 | 取值 | 实测来源 |
|---|---|---|
--materin-surface | var(--background-primary, #0b0f19) | 三者一致 |
--materin-surface-2 | var(--background-secondary, #0f1524) | 三者一致 |
--materin-surface-3 | var(--background-secondary-alt, #131b2e) | 三者一致 |
--materin-surface-4 | var(--background-modifier-hover, #18223a) | 三者一致(悬停底色) |
--materin-border | var(--background-modifier-border, #23304d) | 三者一致 |
--materin-text | var(--text-normal, #e7ecf5) | 三者一致 |
--materin-text-muted | var(--text-muted, #93a1bd) | 三者一致 |
语义状态
| 令牌 | 取值 | 实测来源 |
|---|---|---|
--materin-ok | var(--color-green, #3a7d44) | Office 用 --background-modifier-success,View 用 --color-green,Ctx 写死 #3a7d44 —— 三套写法归一 |
--materin-warn | var(--color-yellow, #b8860b) | 同上;Ctx 写死的 #b8860b 正是这个语义色 |
--materin-error | var(--color-red, #f93e3e) | Office 用 --background-modifier-error,View / Ctx 用 --color-red |
透明与半透明
| 令牌 | 取值 | 用在哪 |
|---|---|---|
--materin-glass-bg | 表面色 74% 透明 | 顶部栏:压在滚动内容上还要看得清 |
--materin-glass-bg-strong | 表面色 90% 透明 | 代码块、按钮、表头 —— 需要读准的地方用高不透明 |
--materin-glass-bg-subtle | 次要表面色 66% 透明 | 卡片、标签、提示条、页脚 |
--materin-glass-border | 描边色 80% 透明 | 玻璃面的边,比实心描边更轻 |
--materin-glass-highlight | 白色 12% 透明 | 卡片斜向高光:玻璃要有「面」的质感,不能只是一块半透明 |
--materin-glass-blur | blur(20px) saturate(180%) | 顶部栏、页脚 |
--materin-glass-blur-sm | blur(12px) saturate(160%) | 卡片、按钮、胶囊、提示条 |
--materin-elev-1 / --materin-elev-2 | 两级阴影 | 半透明面靠阴影分层;悬停时从 1 升到 2 |
形状、间距与字体
| 令牌 | 取值 | 说明 |
|---|---|---|
--materin-radius | var(--radius-s, 6px) | 三个插件的按钮、输入框已统一用 --radius-s,保留 |
--materin-radius-lg | 14px | 卡片、面板等大块面 |
--materin-space-1..5 | 4 / 8 / 12 / 16 / 24 px | 间距只取这五档,不允许写 7px、13px |
--materin-font-ui | var(--font-interface, inherit) | 界面字体跟随宿主 |
--materin-font-small | var(--font-ui-small, 13px) | 次要文字 |
--materin-font-mono | var(--font-monospace, …) | 等宽:路径、令牌名、接口片段 |
HTTP 方法色(View 专用)
Swagger 的方法色是语义色而不是装饰色 —— GET 永远是那个蓝。所以它固定不随主题变,但也必须走令牌:
| 令牌 | 值 |
|---|---|
--materin-method-get | #61affe |
--materin-method-post | #49cc90 |
--materin-method-put | #fca130 |
--materin-method-delete | #f93e3e |
--materin-method-patch | #50e3c2 |
--materin-method-other | #9012fe |
组件命名契约
界面要能被「按名查找、按名维护」,前提是名字本身就是说明书。所以只允许这几种前缀:
| 类型 | 模式 | 例子 |
|---|---|---|
| 组件 | materin-<scope>-<component>[__part][--variant] | materin-site-card / materin-site-card__meta / materin-site-btn--primary |
| 布局 / 工具 | materin-u-<name> | materin-u-wrap / materin-u-grid / materin-u-mono |
| 状态 | is-<state>,或优先用 ARIA 属性 | materin-site-chip[aria-pressed="true"] / .is-active |
scope 只有六个:ui(共享件)、site(本站 UI)、docs(组件库文档站)、office、view、ctx。名字里必须带 scope,这样在全仓库范围搜 materin-ctx- 就能一次拿到该插件的全部 UI。
不用裸类名(.card、.btn 这种),也不做「换个前缀的别名」—— 一个组件只有一个名字,否则按名查找会落空。i18n 的 lang-zh / lang-en 是系统约定,不属于组件,不参与改名。
实况
| 范围 | 实测 | 状态 |
|---|---|---|
| Materin Office | styles.css 534 行 / 76 个类名 | 全部已是 materin-office-* ✓ |
| Materin View | styles.css 608 行 / 108 个类名 | 多数已是 materin-view-*;materin-chip、materin-doc-* 缺 scope 段,待改为 materin-view-* |
| Materin Ctx | styles.css 135 行 / 20 个类名 | 全部已是 materin-ctx-* ✓ |
| 本站 | 48 个类名 | 本轮全部纳入契约,运行时扫描零遗留 ✓ |
清单与守门
每个组件登记在 组件清单:名字、范围、用途、部件、变体、状态、所在文件。清单不是文档,是给工具读的 —— 校验脚本提交前跑一遍,五项检查:
- 契约:所有类名符合上面的模式(裸类名直接报错)
- 存在:用到的组件必须在 CSS 里有定义
- 注册:CSS 里定义的组件必须登记进清单
- 反向:清单里的名字必须真的存在(防止改完名字忘了改清单)
- 令牌:组件层不得出现裸色值
当前结果:33 条组件注册、49 个选择器、48 个类名,全部通过。这就是「按名维护」能成立的原因 —— 名字和实现不允许对不上。
透明与半透明
视觉按 macOS 一贯的透明语言做:界面分层,靠背景模糊和描边把层次交代清楚,而不是靠纯色块堆叠。分四层:
- 底色:不透明,负责整体明暗
- 玻璃面:74%(顶栏)/66%(卡片)透明 + 背景模糊,让下层内容透出一点
- 高光边:卡片左上到右下的一层白 12% 斜向渐变,加正视作「面」而不是「洞」
- 阴影:半透明面必须配阴影,否则浮不起来;悬停时阴影升一级并抬升 3px
透明度不是越低越好看。文字要读得清,所以实测了对比度:
| 主题 | 卡片正文 | 卡片标题 | 顶栏导航 |
|---|---|---|---|
| 深色 | 7.13 : 1 | 15.65 : 1 | 7.36 : 1 |
| 浅色 | 5.51 : 1 | 17.12 : 1 | 5.29 : 1 |
全部高于无障碍标准要求的 4.5 : 1。做法是「玻璃底 + 实色文字」:文字永远用 --materin-text,不给文字加透明度 —— 那是最容易把可读性做没的一步。
降级
- 宿主不支持
backdrop-filter(老内核):令牌自动换成不透明表面,布局、文字、层级不变 - 用户系统开了「减少透明度」(
prefers-reduced-transparency: reduce):同上,自动退回不透明 - 两种降级都只改令牌值,组件层不需要写任何一条额外规则 —— 这也是把透明度做成令牌的原因
待统一项
下表是拉下三个仓库的 styles.css 逐条核对后的结果。规范已定,代码待改:
| 项 | 现状 | 统一为 |
|---|---|---|
| 强调色语义 | Office / View 用 --color-accent,Ctx 用 --interactive-accent | --materin-accent |
| 状态色 | Office 用 --background-modifier-error/warning,View 用 --color-red/green,Ctx 写死三个色值 | --materin-ok / warn / error |
| 硬编码色值 | Office 的 #1f1f1f / #ffffff(表格与按钮文字) | --materin-text / --materin-on-accent |
| 方法色 | View 里写死的 Swagger 六色 | --materin-method-*(值不变,改为令牌) |
| 组件命名 | View 的 materin-chip / materin-doc-* 缺 scope 段 | materin-view-chip / materin-view-doc-* |
| 圆角 | 三者已统一用 var(--radius-s) | 保持,纳入 --materin-radius |
怎么用
正本在哪:令牌、组件清单、校验脚本的正本在仓库 materin-tech/materin-ui;本站与三个插件拿到的都是同步副本(本站的同步脚本把令牌、清单、校验脚本一起带过来并自动校验)。改令牌一律改正本,再同步过来。Vue 3 组件库与它的文档站(组件演示、令牌实时取值)也在这个仓库里。
网页:在站点自己的样式之前引入令牌表 ——
<link rel="stylesheet" href="/assets/css/materin-ui.css">
<link rel="stylesheet" href="/assets/css/style.css">
插件:把令牌文件里的 :root 段复制到插件 styles.css 顶部。不做运行时远程 @import:Obsidian 插件打包成单文件,而且要能离线用,远程依赖只会变成新的故障点。
之后新增 UI:只用 --materin-*;组件按契约命名并登记进清单;提交前跑一次校验脚本。三条都做到,三个插件就不会再各自漂移。
Three principles
Colours follow, shape is fixed
Colours come from the host theme: switch themes in Obsidian and the plugins follow, with no code change; on the web there is no host variable, so brand defaults apply. Radius and spacing go the other way — fixed, no improvising.
One place to restyle
No raw colour values in UI code (#1f1f1f and friends) — everything references --materin-*. Change one file and all three plugins plus this site change with it.
Components you can find by name
Every component has a unique, purpose-bearing name and an entry in the registry. People and AI can both locate it and change it by name — without touching the wrong rule or missing one.
Tokens
The rule is host-first, brand-second: the value listed is the fallback used when the host variable is absent. The swatches below reference the tokens directly, so what you see is the real value in the current theme.
Accent
| Token | Value | Where it came from |
|---|---|---|
--materin-accent | var(--interactive-accent, #5b8cff) | Office / View used --color-accent, Ctx used --interactive-accent; clickable and selectable states should follow --interactive-accent |
--materin-accent-soft | accent at 22% alpha | Derived from the accent; tints and selected states |
--materin-on-accent | var(--text-on-accent, #ffffff) | Text on solid buttons — stop hardcoding white elsewhere |
--materin-secondary | var(--color-cyan, #6ee7d5) | Secondary emphasis: tags, hero accent |
Surfaces and text
| Token | Value | Where it came from |
|---|---|---|
--materin-surface | var(--background-primary, #0b0f19) | Consistent across all three |
--materin-surface-2 | var(--background-secondary, #0f1524) | Consistent across all three |
--materin-surface-3 | var(--background-secondary-alt, #131b2e) | Consistent across all three |
--materin-surface-4 | var(--background-modifier-hover, #18223a) | Consistent (hover fill) |
--materin-border | var(--background-modifier-border, #23304d) | Consistent across all three |
--materin-text | var(--text-normal, #e7ecf5) | Consistent across all three |
--materin-text-muted | var(--text-muted, #93a1bd) | Consistent across all three |
Semantic states
| Token | Value | Where it came from |
|---|---|---|
--materin-ok | var(--color-green, #3a7d44) | Office used --background-modifier-success, View used --color-green, Ctx hardcoded #3a7d44 — three spellings, one meaning |
--materin-warn | var(--color-yellow, #b8860b) | Same story; Ctx's hardcoded #b8860b was exactly this semantic colour |
--materin-error | var(--color-red, #f93e3e) | Office used --background-modifier-error, View / Ctx used --color-red |
Transparent and translucent
| Token | Value | Where it is used |
|---|---|---|
--materin-glass-bg | surface at 74% alpha | Top bar: has to stay readable while content scrolls under it |
--materin-glass-bg-strong | surface at 90% alpha | Code blocks, buttons, table headers — anywhere precision matters |
--materin-glass-bg-subtle | secondary surface at 66% alpha | Cards, tags, callouts, footer |
--materin-glass-border | border at 80% alpha | Edges of glass surfaces — lighter than a solid border |
--materin-glass-highlight | white at 12% alpha | Diagonal sheen on cards: glass should read as a surface, not just a translucent patch |
--materin-glass-blur | blur(20px) saturate(180%) | Top bar, footer |
--materin-glass-blur-sm | blur(12px) saturate(160%) | Cards, buttons, chips, callouts |
--materin-elev-1 / --materin-elev-2 | two shadow levels | Translucent surfaces need shadow to layer; hover lifts level 1 to level 2 |
Shape, spacing and type
| Token | Value | Notes |
|---|---|---|
--materin-radius | var(--radius-s, 6px) | All three plugins already use --radius-s for buttons and inputs; kept as is |
--materin-radius-lg | 14px | Cards and panels |
--materin-space-1..5 | 4 / 8 / 12 / 16 / 24 px | Spacing comes from these five steps only — no 7px, no 13px |
--materin-font-ui | var(--font-interface, inherit) | UI font follows the host |
--materin-font-small | var(--font-ui-small, 13px) | Secondary text |
--materin-font-mono | var(--font-monospace, …) | Monospace: paths, token names, endpoint snippets |
HTTP method colours (View only)
Swagger's method colours are semantic, not decorative — GET is always that blue. So they stay fixed across themes, but they still go through tokens:
| Token | Value |
|---|---|
--materin-method-get | #61affe |
--materin-method-post | #49cc90 |
--materin-method-put | #fca130 |
--materin-method-delete | #f93e3e |
--materin-method-patch | #50e3c2 |
--materin-method-other | #9012fe |
Component naming contract
UI that can be found and maintained by name needs names that document themselves. Only these prefixes are allowed:
| Kind | Pattern | Examples |
|---|---|---|
| Component | materin-<scope>-<component>[__part][--variant] | materin-site-card / materin-site-card__meta / materin-site-btn--primary |
| Layout / utility | materin-u-<name> | materin-u-wrap / materin-u-grid / materin-u-mono |
| State | is-<state>, ARIA attributes preferred | materin-site-chip[aria-pressed="true"] / .is-active |
There are six scopes: ui (shared), site (this site), docs (the component library's docs site), office, view, ctx. The scope is mandatory, so grepping materin-ctx- across the repos returns that plugin's entire UI in one go.
No bare class names (.card, .btn), and no prefixed aliases either — one component, one name, otherwise a lookup by name silently misses. The i18n pair lang-zh / lang-en is a system convention, not a component, and is exempt from renaming.
Where things stand
| Scope | Measured | Status |
|---|---|---|
| Materin Office | styles.css 534 lines / 76 class names | Already all materin-office-* ✓ |
| Materin View | styles.css 608 lines / 108 class names | Mostly materin-view-*; materin-chip and materin-doc-* lack a scope segment — to become materin-view-* |
| Materin Ctx | styles.css 135 lines / 20 class names | Already all materin-ctx-* ✓ |
| This site | 48 class names | All brought under the contract this round; a runtime scan finds zero leftovers ✓ |
Registry and gatekeeper
Every component is registered in the component registry: name, scope, purpose, parts, variants, states, file. The registry is not documentation — it is read by tooling. The checker runs five checks before each commit:
- Contract: every class name matches the pattern (bare names fail)
- Existence: anything used must be defined in CSS
- Registration: anything defined in CSS must be in the registry
- Reverse: every registry name must actually exist (catches a half-finished rename)
- Tokens: no raw colour values in the component layer
Current result: 33 registered components, 49 selectors, 48 class names — all passing. That is what makes maintenance by name credible: a name and its implementation are not allowed to drift apart.
Transparent and translucent
The visual follows the translucency language macOS has been building for years: interface layered with background blur and light edges, rather than stacked opaque blocks. Four layers:
- Base: opaque, sets overall lightness
- Glass surfaces: 74% (top bar) / 66% (cards) alpha plus background blur, so content below shows through slightly
- Specular edge: a diagonal white-12% gradient on cards, so they read as a surface rather than a hole
- Shadow: translucent surfaces need shadow or they never lift off; hover raises one level and translates 3px
Lower opacity is not automatically better — text has to stay readable, so contrast was measured:
| Theme | Card body | Card title | Top-bar nav |
|---|---|---|---|
| Dark | 7.13 : 1 | 15.65 : 1 | 7.36 : 1 |
| Light | 5.51 : 1 | 17.12 : 1 | 5.29 : 1 |
All above the 4.5 : 1 accessibility floor. The trick is glass underneath, solid text on top: text always uses --materin-text and is never given transparency — that is the one step that quietly destroys readability.
Fallbacks
- Host without
backdrop-filter(older engine): tokens switch to opaque surfaces — layout, text and layering unchanged - System-level "Reduce transparency" (
prefers-reduced-transparency: reduce): same, falls back to opaque - Both fallbacks only change token values; the component layer needs no extra rules — which is exactly why translucency was made a token
What is still to unify
The table below comes from reading all three repositories' styles.css line by line. The spec is settled; the code is not yet:
| Item | Today | Should be |
|---|---|---|
| Accent semantics | Office / View use --color-accent, Ctx uses --interactive-accent | --materin-accent |
| State colours | Office uses --background-modifier-error/warning, View uses --color-red/green, Ctx hardcodes three values | --materin-ok / warn / error |
| Hardcoded colours | Office's #1f1f1f / #ffffff (table and button text) | --materin-text / --materin-on-accent |
| Method colours | View hardcodes Swagger's six colours | --materin-method-* (same values, now tokens) |
| Component naming | View's materin-chip / materin-doc-* have no scope segment | materin-view-chip / materin-view-doc-* |
| Radius | All three already use var(--radius-s) | Kept, folded into --materin-radius |
How to use it
Where the source is: the tokens, the component registry and the checker live in materin-tech/materin-ui; this site and the three plugins hold synced copies (the site's sync script pulls tokens, registry and checker across and re-runs the checks). Change a token in the source repo, then sync. The Vue 3 component library and its docs site (component demos, live token values) live in that same repository.
On the web: link the token sheet before the site's own styles —
<link rel="stylesheet" href="/assets/css/materin-ui.css">
<link rel="stylesheet" href="/assets/css/style.css">
In a plugin: copy the :root block from the token file to the top of the plugin's styles.css. No runtime remote @import — Obsidian plugins ship as a single bundle and must work offline; a remote dependency would just be another failure mode.
For new UI: reference --materin-* only; name components per the contract and register them; run the checker before committing. Do those three and the plugins stop drifting apart.