起因是Flarum中文社区2群里面有佬说;
“你用 composer 把 flarum/frontend 卸载了”
“然后对着 ai 说”
“你按照 flarum/frontend 重构一个我喜欢的样子”
“flarum 的整个界面都是这个包负责的”
然后我就喊 DeepSeek 研究了~
Flarum 界面研究 & 本地 Demo
对 flarum-v2.0.2-php8.5 的前端源码做了一次通读,并用它自己的 LESS 源码编译出一套样式,
再按照 Mithril 组件的 DOM 结构手写了一份静态页面,用来直观地看 Flarum 的 UI 风格。
打开方式:双击
site/index.html即可(纯静态,CSS / JS / 字体都在本地,无需联网、无需 PHP)。
如果浏览器对file://比较严格,也可以在site/里跑python3 -m http.server 8000,然后访问
<http://localhost:8000>。

1. 一个前提:Flarum 2.x 已经没有 flarum/frontend 了
你听说的「用 composer 卸载 flarum/frontend,然后让 AI 照着重构」,是 Flarum 1.x 时代的做法。
1.x 里前端(JS + LESS)是独立 composer 包 flarum/frontend;到了 2.0,它被合并进了 flarum/core:
vendor/flarum/core/
├── js/ # 前端源码(Mithril 组件,TypeScript)
│ ├── src/common/ # 通用组件:Button / Dropdown / Modal / Avatar / Badge ...
│ ├── src/forum/ # 论坛侧:IndexPage / DiscussionPage / PostStream / Composer ...
│ ├── src/admin/ # 管理后台
│ └── dist/ # 编译产物 forum.js / admin.js
├── less/ # ★ 设计系统就在这
│ ├── common/ # 变量、混入、按钮、下拉、表单、弹窗……
│ ├── forum/ # 首页、帖子、帖子流、Hero……
│ ├── admin/
│ ├── forum.less # 论坛样式入口
│ └── admin.less
└── views/frontend/ # 服务端输出的 HTML 骨架(header / drawer / content)
所以「设计文件在 frontend 里」这个说法,在 2.x 应该改成「设计文件在 flarum/core/js + flarum/core/less 里」。
扩展(tags、likes、sticky……)各自还带自己的 less/ 和 js/src/,会往上叠加。
比如讨论列表右侧那一列彩色标签,就是 flarum/tags 扩展的 less/common/TagLabel.less 做的。
2. 这套 Demo 是怎么来的(没有手抄样式)
真实 Flarum 的 CSS 是这样产出的:PHP 里的 LessCompiler 把
less/common/variables.less → less/common/mixins.less → less/forum.less 依次编译。
这里用同样的顺序、同样的入口,用 lessc 直接编:
build/entry.less→site/assets/flarum.css(核心,约 4,000 条规则)build/entry-tags.less→site/assets/tags.css(标签扩展)- Font Awesome 7 的字体直接从
vendor/flarum/font-awesome/webfonts拷过来
也就是说:页面上的颜色、字号、间距、圆角、hover、动画,全部是真货,不是照着截图仿的。
HTML 部分则是照着 Mithril 组件的 view() 还原的 DOM,例如:
| 组件 | 文件 | 本 Demo 对应 |
|---|---|---|
PageStructure | js/src/forum/components/PageStructure.tsx | .Page > .Page-main > .Page-hero + .Page-container |
HeaderSecondary / HeaderDropdown | js/src/forum/components/* | 顶栏搜索 / 主题 / 通知 / 头像 |
IndexPage + IndexSidebar | js/src/forum/components/IndexPage.tsx | 首页侧栏、排序工具条、讨论列表 |
DiscussionListItem | js/src/forum/components/DiscussionListItem.tsx | 列表行:头像、置顶徽章、标题、标签、回复数 |
DiscussionHero / Hero | js/src/forum/components/DiscussionHero.tsx | 帖子页顶部彩色横幅 |
PostStream / CommentPost / PostUser / PostMeta | 同上 | 楼层、头像列、时间下拉、悬停操作 |
PostStreamScrubber | 同上 | 右侧「第一条 / 正在查看 1 / 4 / 最后一条」 |
重新编译样式:
cd build && ./compile.sh # 会自动 npm i less,然后覆盖 site/assets/*.css
3. Flarum 的设计语言(从源码里量出来的)
颜色
主题色只有一个入口:@config-primary-color / @config-secondary-color(默认都是 #536F90,
后台「外观」里改的就是这两个值),其余颜色全部由它们推导:
| Token | 亮色 | 暗色 |
|---|---|---|
--body-bg | #fff | hsl(212.46, 20%, 10%) ≈ #14191f |
--text-color | #111 | #ddd |
--control-bg | hsl(212.46, 26.87%, 93%) | hsl(212.46, 20%, 13%) |
--control-color | hsl(212.46, 20%, 50%) | 同左 |
--muted-color | hsl(212.46, 20%, 50%) | hsl(212.46, 15%, 50%) |
--link-color | saturate(#536F90, 10%) | 同左 |
--discussion-title-color | mix(#111, #fff, 55%) | mix(#ddd, #14191f, 55%) |
--shadow-color | rgba(0,0,0,.35) | rgba(0,0,0,.5) |
暗色不是「反色」,而是重新取一组 HSL:饱和度压到 20% 以下、亮度压到 10%,
所以蓝灰的主色在暗色下会自然变成低饱和的深蓝黑。
主题通过 <html data-theme="light|dark|light-hc|dark-hc"> 切换,auto 跟随系统;
本 Demo 右上角的「太阳/月亮」下拉就是这套机制(选择会存进 localStorage)。
尺寸 / 节奏
| Token | 值 | 说明 |
|---|---|---|
| 基础字号 | 13px,行高 1.5 | body;正文(.Post-body)是 14px / 1.7,≥1100px 时 15px |
| 讨论标题 | 16px(手机 14px) | 未读 600,已读为 --discussion-title-color |
| 顶栏 | 52px(手机 46px) | --header-height,所有吸顶元素都对齐它 |
| 圆角 | 4px | --border-radius,按钮、标签、输入框、代码块统一 |
| 间距 | 20px | --post-padding、.Post 内边距 |
| 头像列 | 85px | --avatar-column-width(桌面 64px 头像 + 21px 间隙) |
| 侧栏 | 190px → 260px(HD)→ 280px(XXL) | --sidebar-width,主内容间距 50px(HD 56px) |
| 断点 | 768 / 992 / 1100 / 1600 / 2000 / 3000 | 容器宽 1200 / 1300 / 1500 |
| 层级 | 1000 → 1070 | header → pane → composer → dropdown → modal → alert → tooltip |
几个「很 Flarum」的细节(Demo 里都能看到)
- 头像:没有上传头像时,用用户名的首字母 + 由用户名哈希出的颜色(
stringToColor()),
所以「林小满」永远是那颗淡粉色的「林」。 - 按钮:默认是淡灰底(
--control-bg)+ 4px 圆角;主按钮只改底色为--primary-color,
并加粗、左右内边距加到 20px。 - 徽章:22px 的圆形,
--badge-bg由群组颜色给;置顶帖的图钉、管理员的扳手都在这。 - 标签:列表里靠右浮在标题行(
position:absolute; right:80px),帖子里变成白底彩字的 pill。 - 讨论页 Hero:有标签时,第一条标签的颜色会成为整个 Hero 的背景色(
--hero-bg),
文字自动选反色 —— Demo 里那条蓝底就是「讨论」标签的#4A6FA5。 - 右侧 Scrubber:桌面端不是滚动条,是一条 300px 高的竖向轨道 + 蓝色把手,
上下各有「第一条 / 最后一条」。 - 悬停才出现的操作:列表行的
⋮、楼层右下角的「赞 / ⋮」默认opacity: 0,
只有 hover /:focus-within时才浮出来 —— 鼠标不在时才干净。
4. Demo 里能点什么
| 操作 | 结果 |
|---|---|
| 点列表标题 | 进入帖子页(#/d/2),该条自动变为已读 |
| 右上角太阳/月亮 | 跟随系统 / 浅色 / 深色 / 两种高对比度,立即生效 |
| 铃铛 | 通知面板(3 条未读,可「全部标为已读」) |
| 头像 | 用户菜单(个人主页 / 设置 / 后台 / 退出) |
| 侧栏标签 | 直接筛选列表(再点「全部讨论」恢复) |
| 排序下拉 | 切换「最新回复 / 最新发布 / 最多回复 / 最早发布」 |
| 工具条的对勾 | 全部标为已读(角标一起消失) |
| 列表行 hover | 出现 ⋮ 菜单,可「标为已读」 |
| 「开始讨论」 | 打开 Flarum 风格的弹窗表单 |
| 帖子页「写下你的回复…」 | 弹窗提交后,真的会插入一个新楼层,并更新 Scrubber 计数 |
| 楼层时间 | 展开 #编号 / 完整时间 / IP / 永久链接 |
| 手机宽度(<768px) | 顶栏变汉堡菜单 + 居中标题 + 右侧主按钮,抽屉、横向侧栏都按 Flarum 的断点走 |
5. 目录
flarum-ui/
├── site/ # 演示站点(打开 index.html)
│ ├── index.html # 首页 + 帖子页(hash 路由)
│ ├── assets/
│ │ ├── flarum.css # ← 由 vendor/flarum/core/less 编译
│ │ ├── tags.css # ← 由 vendor/flarum/tags/less 编译
│ │ ├── demo.css # 只补了 demo 脚手架(hidden、抽屉遮罩等)
│ │ ├── app.js # 主题 / 下拉 / 路由 / 弹窗 / 发回复
│ │ └── avatars/ # 两张示例头像
│ └── webfonts/ # Font Awesome 7(来自 vendor)
├── build/ # 重新编译 CSS 用
│ ├── entry.less
│ ├── entry-tags.less
│ └── compile.sh
└── screenshots/ # 首页/帖子页/暗色/手机/弹窗
6. 已知的简化
- 没有后端:搜索是占位弹窗,分页是「加载更多」按钮,登录状态直接写死为「林小满」。
- 回复用的是 Flarum 的 Modal 样式;官方是贴底悬浮的
Composer(需要编辑器、自动保存等,
属于另一套交互,这里没有搬)。 - 没有引入 jQuery / Bootstrap / Mithril —— 只复用了 CSS 和 DOM 约定,交互由 100 多行原生 JS 完成。
- 通知面板、悬停菜单的条目是示意数据。
Flarum UI 设计指南
基于 Flarum 2.0.2 源码整理(
vendor/flarum/core/less+vendor/flarum/core/js/src),
文中所有数值、类名、变量名都能在源码里逐条对上。
配图来自按源码还原的本地 Demo 与组件速查表(样式是 Flarum 原版 LESS 的编译产物)。
适合谁看:要基于 Flarum 做主题 / 扩展 / 二创的设计师与前端;
想理解「为什么 Flarum 长这样」的产品同学。
目录
1. 设计原则
Flarum 的界面代码里有几条非常稳定的「隐性规则」。理解了它们,几乎不需要背具体数值。
1.1 Token 先行
界面上出现的每个颜色、圆角、层级都是 CSS 变量,组件只引用变量:
.Button {
color: var(--button-color);
background: var(--button-bg);
border-radius: var(--border-radius);
}
主题、暗色模式、高对比度都只是换一套变量值,不碰组件样式。
1.2 一个主色,推导全站
后台「外观」里只让你填两个色值(primary / secondary),
剩下的控制色、链接色、悬停色、遮罩色全部由它们推导出来:
@primary-color: @config-primary-color; // #536F90
@secondary-color: @config-secondary-color; // #536F90
@link-color: saturate(@primary-color, 10%); // #486e9b
@control-bg: hsl(@secondary-hue, min(50%, @secondary-sat), 93%);
@muted-color: hsl(@secondary-hue, min(20%, @secondary-sat), 50%);
@body-bg-dark: hsl(@secondary-hue, min(20%, @secondary-sat), 10%);
1.3 语义命名,而不是外观命名
✅ --control-bg --muted-color --discussion-title-color
❌ --grey-100 --blue-500 --title-grey
好处是同一个 token 在亮色 / 暗色下可以取完全不同的值,而组件毫无感知。
1.4 克制的层级
- 全局只有一种圆角:
4px(--border-radius)。 - 全局只有三种阴影:下拉/顶栏
0 2px 6px、弹窗0 7px 15px、徽章0 2px 4px。 - 层级主要靠字号、字重、留白表达,不靠边框和卡片。
- 控件高度统一 36px(按钮、输入框、下拉触发器都一样高)。
1.5 内容优先,操作后置
- 讨论列表没有卡片、没有分割线,行与行只靠间距分开。
- 每行的
⋮菜单、每个楼层的「赞 / ⋮」默认opacity: 0,hover或:focus-within才出现。 - 顶栏在滚动前没有阴影,滚动后才加。
.Post-actions {
transition: opacity .2s;
@media @tablet-up { .no-touch & { opacity: 0; } }
.Post:hover &, .Post:focus-within &, &.openWithin { opacity: 1; }
}
1.6 暗色是「重新调色」,不是反色
暗色不是 filter: invert(),也不是把亮色简单压暗,而是每个 token 重新取一组 HSL:
饱和度压到 20% 以下、亮度压到 10%~16%。所以主色在暗色里会「沉」下去,而不是跳出来。
1.7 可访问性是内建的
- 所有可交互元素都有键盘焦点环(
outline: 5px auto Highlight,只在:focus-visible出现)。 - 图标按钮强制带
aria-label;纯装饰图标带aria-hidden。 - 页面有 skip link(跳到主内容);弹窗有 focus trap、
role="dialog"、aria-modal。
2. 颜色系统
2.1 推导链
config-secondary ──┬── control-bg (控件底色:按钮、输入框、代码块)
├── muted-color (次要文字:时间、说明、占位)
├── body-bg-dark (暗色背景)
└── overlay-bg (遮罩:弹窗、抽屉)
config-primary ────┼── link-color (链接、可点击文字)
├── button-primary (主按钮)
└── badge/hero 等强调色
2.2 核心 Token(亮色 / 暗色)
| Token | 亮色 | 暗色 | 用途 |
|---|---|---|---|
--body-bg | #ffffff | #14191f | 页面底色 |
--body-bg-shaded | #f7f7f7 | #0e1215 | 比底色更沉的区域 |
--body-bg-light | #ffffff | #1f262e | 比底色更亮的区域 |
--control-bg | #e8edf2 | #1b2128 | 按钮、输入框、代码块底色 |
--control-bg-shaded | #dbe2ea | #12171c | 控件按下态 / 分隔线 |
--control-color | #667d99 | #6c7e93 | 控件上的文字 |
--muted-color | #667d99 | #6c7e93 | 次要文字 |
--muted-more-color | #aaaaaa | #5c6570 | 元信息(时间、回复数说明) |
--text-color | #111111 | #dddddd | 正文 |
--link-color | #486e9b | #486e9b | 链接(主色饱和 +10%) |
--discussion-title-color | #7c7c7c | #838587 | 已读讨论标题 |
--primary-color | #536F90 | #536F90 | 主色 |
--shadow-color | rgba(0,0,0,.35) | rgba(0,0,0,.5) | 阴影 |
--overlay-bg | rgba(83,111,144,.9) | hsla(212,20%,5%,.9) | 弹窗 / 抽屉遮罩 |
--code-bg / --code-color | #f7f7f7 / #5e5e5e | #0e1215 / #fff | 行内代码、代码块 |
--highlight-color | #FFE300 | #FFE300 | 搜索命中高亮 |
2.3 语义色(不随主题变化)
| 语义 | 背景 | 文字 | 用途 |
|---|---|---|---|
| Success | #B4F1AF | #33722D | 成功按钮、成功提示 |
| Warning | #fff2ae | #ad6c00 | 普通提示条(Alert 默认色) |
| Error | #d83e3e | #fff | 错误提示、危险按钮、表单错误 |
| Online | #7FBA00 | — | 在线小绿点 |
注意:这三个语义色在暗色模式下不反色,因为它们本身是「带白/黑文字的高饱和块」。
若你的主题要重定义,记得同时检查块内文字对比度。
2.4 主题切换机制
主题挂在 <html> 上,一行属性切换整套变量:
<html data-theme="light"> <!-- 亮色 -->
<html data-theme="dark"> <!-- 暗色 -->
<html data-theme="light-hc"> <!-- 亮色高对比度 -->
<html data-theme="dark-hc"> <!-- 暗色高对比度 -->
对应选择器:
[data-theme^=light] { .scheme(light); }
[data-theme^=dark] { .scheme(dark); }
[data-theme=light-hc] { ... } // 在 light 基础上加深文字、加强对比
[data-theme=dark-hc] { ... }
auto 不是一套变量,而是由 JS 解析成 light / dark 后写入 data-theme。
所以任何 Theme 只要提供了这四套变量,就自动支持主题切换。

2.5 用色规则
- 不要在组件里写 hex,一律
var(--token)。 - 背景用
--body-bg/--control-bg;文字用--text-color/--muted-color。 - 需要「比底色深一点」时用
--body-bg-shaded,不要手动darken()。 - 彩色块(标签、徽章)必须同时设置前景,参照
text-contrast--light/dark的做法。
对比度参考(默认主题实测):
| 组合 | 对比度 | 说明 |
|---|---|---|
--text-color on --body-bg | 18.9 : 1 | 正文,非常安全 |
白字 on --primary-color | 5.19 : 1 | 主按钮,达标 |
--muted-color on 白 | 4.24 : 1 | 次要文字,接近 AA 边界 |
--muted-more-color on 白 | 2.32 : 1 | ⚠️ 仅用于时间、计数等元信息,别放正文 |

3. 排版
3.1 字体栈
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Ubuntu, Cantarell, Oxygen, Roboto, Helvetica, Arial, sans-serif;
不引入 Web Font(除图标字体),保证首屏零字体请求。
中文由系统字体(macOS 苹方 / Windows 微软雅黑)接管,数字与英文走系统 UI 字体。
3.2 字号阶梯
| 角色 | 字号 | 字重 | 行高 | 颜色 |
|---|---|---|---|---|
| Hero 标题 | 22px(手机 16px) | 400 | 1.5em | 继承(彩色 Hero 下自动反色) |
| 讨论标题(未读) | 16px(手机 14px) | 600 | 1.3 | --heading-color |
| 讨论标题(已读) | 16px | 400 | 1.3 | --discussion-title-color |
| 正文(帖子) | 14px,≥1100px 为 15px | 400 | 1.7 | --text-color |
| 界面文本 | 13px | 400 | 1.5 | --text-color |
| 表单标签 | 14px | 700 | 1.5 | --text-color |
| 辅助说明 | 12px | 400 | 1.5 | --muted-color |
| 列表元信息 | 11px | 400 | 1.5 | --muted-more-color |
| 按钮 | 13px | 400 / 700(主按钮) | 20px | --button-color |
| 行内代码 | 90% | 400 | 1.3 | --code-color |
帖子正文内部的标题(Markdown 渲染):
h1 225% · h2 175% · h3 150% · h4 125% · h5/h6 100%
3.3 行长与留白
- 宽屏下讨论正文被限制在
100ch(--discussion-content-max-width),
容器可以继续变宽,但一行文字不会变长。 - 段落间距
1em,列表、引用块也是margin-bottom: 1em。 - 帖子在桌面端有 64px 的头像列(
--avatar-column-width: 85px),正文从列右侧开始。
3.4 链接与强调
a { cursor: pointer; color: var(--link-color); text-decoration: none; }
a:hover { text-decoration: underline; }
- 正文里的链接额外加
1px底边,hover 时底边变主色(替代下划线,中文阅读更干净)。 - 强调用
<strong>/<em>,不要用颜色区分语义。 - 搜索命中用
<mark>:黄底 + 4px 圆角 + 1px 内阴影。
4. 形状与节奏
4.1 圆角
| 值 | 用途 |
|---|---|
4px(--border-radius) | 默认:按钮、输入框、下拉、标签、提示条、代码块 |
18px | Button--rounded、Button--flat(胶囊按钮) |
14px | Switch 滑块 |
50% | 头像、徽章 |
4.2 控件高度
| 控件 | 高度 |
|---|---|
| 按钮 / 输入框 / 下拉触发器 | 36px |
紧凑按钮 Button--compact | 28px(4px 上下内边距 + 20px 行高) |
图标按钮 Button--icon | 36 × 36 |
| 顶栏 | 52px(手机 46px) |
| 标签 | 约 18px(85% 字号 + 0.1em 上下内边距) |
| 徽章 | 22px 圆形 |
4.3 间距刻度
实际用到的值基本落在这一组上:
4 · 8 · 10 · 12 · 15 · 20 · 30 · 50(· 55 讨论页栏间距)
常见组合:
- 页面容器左右 padding:15px
- 内容区顶部间距:30px(手机 15px)
- 帖子内边距:20px(
--post-padding) - 下拉菜单项:8px 15px
- 表单组之间:10px 起,区块之间 24px
4.4 阴影
| 场景 | 值 |
|---|---|
| 顶栏滚动后、下拉菜单、抽屉 | 0 2px 6px var(--shadow-color) |
| 弹窗 | 0 7px 15px var(--shadow-color) |
| 徽章、头像、Tooltip 箭头 | 0 2px 4px var(--shadow-color) |
阴影颜色本身是 token(亮色 .35、暗色 .5 黑),所以暗色下阴影更重但不会发灰。
4.5 层级(z-index)
| 层 | 值 |
|---|---|
| 顶栏 | 1000 |
| 讨论列表抽屉(pane) | 1010 |
| 编辑器 | 1020 |
| 下拉菜单 | 1030 |
| 弹窗遮罩 | 1040(+ modal-count) |
| 弹窗 | 1050(+ modal-number) |
| 通知条 | 1060 |
| Tooltip | 1070 |
新增浮层时不要写 9999,从这张表里挑一个相邻的层级。
5. 布局与响应式
5.1 断点
| 名称 | 范围 | 主要变化 |
|---|---|---|
| small-phone | ≤ 320px | 列表信息更窄 |
| phone | < 768px | 抽屉导航;顶栏 46px;标题居中 |
| tablet | 768 – 991px | 侧边栏变为横向导航条;顶栏显示头像不显示用户名 |
| desktop | 992 – 1099px | 双栏布局(侧栏 + 内容) |
| desktop-hd | ≥ 1100px | 正文 15px、正文封顶 100ch;顶栏搜索常驻(手机移入工具条) |
| desktop-xl | ≥ 1600px | 容器 1300px |
| desktop-xxl | ≥ 2000px | 容器 1500px |
5.2 容器宽度
| 断点 | .container 宽度 |
|---|---|
| < 1100px | 流式(左右 15px 内边距) |
| ≥ 1100px | min(1200px, 100%) |
| ≥ 1600px | min(1300px, 100%) |
| ≥ 2000px | min(1500px, 100%) |
5.3 页面骨架
<div class="Page IndexPage">
<div class="Page-pane"></div> <!-- 可选:讨论列表抽屉 -->
<div class="Page-main">
<a class="sr-only sr-only-focusable-custom">跳至主要内容</a>
<div class="Page-hero"> <!-- 通栏 Hero -->
<header class="Hero WelcomeHero">…</header>
</div>
<div class="Page-container container"> <!-- 侧栏 + 内容 -->
<div class="Page-sidebar">…</div>
<div class="Page-content" id="main-content">…</div>
</div>
</div>
</div>
.Page-container是 flex:侧栏宽度190px → 260px(HD) → 280px(XXL),间距50px → 56px(HD)。- 讨论页把
flex-direction反转,侧栏跑到右边,并给内容加100ch上限。 - 手机 / 平板下侧栏不占位,而是变成内容上方的横向导航条(
sideNav--horizontal)。
5.4 顶栏
<header class="App-header">
<div class="container">
<div class="Header-title">…logo / 站名…</div>
<div class="Header-primary">…导航项…</div>
<div class="Header-secondary">…搜索 / 主题 / 通知 / 头像…</div>
</div>
</header>
- 固定定位,高 52px(手机 46px),
z-index: 1000。 .Header-secondary内的控件间距只有 2px:图标按钮固定 36px 宽,文字按钮靠自身 13px 内边距撑开。- 平板区间会自动隐藏用户名、只留头像;搜索按钮收成图标。
- 手机:汉堡(左)+ 页面标题下拉(中)+ 主操作(右,如「开始讨论」),
顶栏内容整体移入左侧抽屉。
5.5 首页
Page.IndexPage
├─ Page-hero ──── WelcomeHero(通栏,--hero-bg 底 + 一句话简介)
└─ Page-main
└─ Page-container.container
├─ Page-sidebar ── 开始讨论 / 全部讨论 / 标签 / ─── / 标签列表
└─ Page-content ── 工具条(排序 · 刷新 · 全部已读)
└─ DiscussionList(行与行之间没有卡片、没有分割线)
└─ 加载更多
5.6 讨论页
Page.DiscussionPage
├─ Page-hero ──── DiscussionHero(背景取第一条标签的颜色,文字自动反色)
└─ Page-container.container(flex-direction: row-reverse → 侧栏在右)
├─ Page-content ── PostStream
│ ├─ 楼层(64px 头像列|用户名·徽章·时间|正文|赞·⋮)
│ ├─ 时间间隔(超过 4 天)
│ ├─ 楼层 …
│ └─ 回复占位
└─ Page-sidebar ── 回复 / ⋮ · 第一条 · Scrubber · 最后一条
帖子内部的结构:
| 区域 | 内容 |
|---|---|
.Post-header | 用户名、群组徽章、时间(点击展开编号 / 完整时间 / IP / 永久链接) |
.Post-side | 64px 头像(手机端隐藏,头像改放在标题行) |
.Post-body | 正文:14px / 1.7,代码块、引用、列表都有独立样式 |
.Post-actions | 赞、⋮,默认透明,hover / focus 才出现 |
.Post-footer | 编辑记录、扩展信息(默认为空) |
5.7 响应式要点
| 元素 | 手机 | 平板 | 桌面 |
|---|---|---|---|
| 侧栏 | 移入顶栏(标题下拉 / 主按钮) | 横向导航条 | 左侧固定列 |
| 讨论列表 | 单行:头像 + 标题 + 回复数气泡 | 完整两行 | 完整两行 + 右侧标签 |
| 帖子头像 | 32px,放在标题行 | 64px 左列 | 64px 左列 |
| 帖子操作 | 常显 | hover 显示 | hover 显示 |
| 弹窗 | 全屏覆盖 | 居中 600px | 居中 600px |



6. 组件规范

6.1 按钮 Button
| 类名 | 外观 | 用在哪 |
|---|---|---|
.Button | 淡灰底、深灰字、4px 圆角 | 默认操作 |
.Button--primary | 主色底、白字、加粗、左右 20px | 页面唯一主操作(开始讨论、发布) |
.Button--link | 透明底、链接色 | 文字型操作、菜单项 |
.Button--flat | 透明底、18px 圆角 | 顶栏图标按钮 |
.Button--icon | 36×36、只有图标 | 刷新、更多、关闭 |
.Button--compact | 4px 10px,更矮 | 表格行内、次级操作 |
.Button--rounded | 18px 圆角 | 胶囊型按钮 |
.Button--danger / --success / --warning | 语义色 | 删除、确认、警告 |
.Button--dashed / --outline | 虚线 / 描边 | 「添加」类空位 |
[disabled] / .disabled | opacity: .65 | 不可用 |
结构:
<button class="Button Button--primary hasIcon" type="button">
<i class="icon fa-solid fa-pen-to-square Button-icon" aria-hidden="true"></i>
<span class="Button-label">
<span class="Button-labelText">开始讨论</span>
<span class="Button-helperText">可选副标题</span>
</span>
</button>
规则:
- 一个区域只放一个
Button--primary。 - 纯图标按钮必须给
aria-label。 - 按钮里不要塞
<div>,文案用.Button-labelText包裹,方便截断。 - 危险操作永远不要和主按钮并排等宽。
6.2 表单
| 元素 | 规格 |
|---|---|
.FormControl | 高 36px、2px 透明边框、8px 13px 内边距、4px 圆角 |
| 聚焦 | 边框变 --primary-color,底色变 --body-bg,文字变 --text-color |
| 占位符 | 用 --control-color,不要用浏览器默认灰 |
| 标签 | 14px / 700,和控件间距 10px |
帮助文本 .helpText | 12px / --muted-color |
错误 .ValidationError | 0.9em / 700 / --validation-error-color |
| 禁用 | opacity: .5 + cursor: not-allowed |
<div class="Form">
<div class="Form-group">
<label>标题</label>
<input class="FormControl" type="text" placeholder="一句话说清楚你想讨论什么">
<div class="helpText">发布后仍可编辑。</div>
</div>
</div>
- 手机端输入框字号强制 16px,防止 iOS 聚焦时页面放大。
- 勾选框不用方框,直接显示 ✓ / ✗(
.Checkbox-display),开关才是滑块。 - 表单里「字段级错误」用红色边框盒子(
.Form-group--danger)+ 红色说明,
「整页错误」用.Alert--error,两者不要混用。
6.3 下拉菜单
<div class="ButtonGroup Dropdown dropdown">
<button class="Button Dropdown-toggle" data-toggle="dropdown">排序</button>
<ul class="Dropdown-menu dropdown-menu">
<li><a class="Button" href="#">最新回复</a></li>
<li class="Dropdown-separator"></li>
<li><button class="Button">最多回复</button></li>
</ul>
</div>
| 规格 | 值 |
|---|---|
| 菜单最小宽度 | 160px |
| 内边距 | 8px 0 |
| 与触发器间距 | 7px |
| 菜单项 | 8px 15px,图标与文字间隔 9px |
| 分隔线 | 1px --control-bg,上下 8px |
| 出现方式 | 无动画,直接显示(避免菜单抖动) |
- 靠近屏幕右侧时加
Dropdown-menu--right,靠底时加Dropdown-menu--top。 - 菜单项里放
<a>/<button>/<span>,样式自动对齐,不要自己加 padding。
6.4 弹窗 Modal
| 规格 | 值 |
|---|---|
| 宽度 | 默认 600px,Modal--small 375px,Modal--large 800px |
| 位置 | 距顶部 120px,水平居中 |
| 圆角 | 4px |
| 阴影 | 0 7px 15px |
| 遮罩 | --overlay-bg(主色 90% 透明) |
| 结构 | header(25px 内边距,20px 标题)/ body(--control-bg 底)/ footer |
| 动画 | transform: scale(.9) → 1 + 淡入,200ms |
<div class="ModalManager modal">
<div class="Modal Modal--small modal-dialog fade in">
<div class="Modal-content">
<div class="Modal-close App-backControl"><button class="Button Button--icon Button--link">×</button></div>
<div class="Modal-header"><h3 class="App-titleControl App-titleControl--text">标题</h3></div>
<div class="Modal-body">…</div>
<div class="Modal-footer">…</div>
</div>
</div>
<div class="ModalManager-invisibleBackdrop"></div>
</div>
<div class="Modal-backdrop backdrop" data-showing></div>
注意:遮罩是弹窗容器的兄弟节点而不是子节点,靠 --modal-count / --modal-number
两个变量排队。手机端弹窗变成全屏页。
6.5 头像、徽章、标签
头像 Avatar:没有上传头像时用「用户名首字符 + 用户名哈希色」。
| 场景 | 尺寸 |
|---|---|
| 顶栏用户菜单 | 24px |
| 手机帖子头部 | 32px |
| 讨论列表 | 36px |
| 默认 / 用户卡片 | 48px |
| 桌面帖子 | 64px |
哈希规则:用户名所有字符的 charCode 之和 % 360 作为色相,s=0.3, v=0.9。
所以同一个用户名永远是同一种颜色。
徽章 Badge:22px 圆、白/黑字自动对比,颜色来自群组色。
| 徽章 | 例子 |
|---|---|
| 管理员 | #B72A2A + fa-wrench |
| 版主 | #80349E + fa-bolt |
| 置顶 | 主色 + fa-thumbtack |
| 默认 | --muted-color + 任意图标 |
标签 TagLabel:85% 字号、0.1em 0.5em 内边距、4px 圆角。
多个标签相邻时中间圆角为 0,拼成一个整体;子标签在导航里会缩进并隐藏色块。
6.6 反馈
| 组件 | 规格 |
|---|---|
.Alert | 12px 16px 内边距、警告黄底;--error 红底白字;--success 绿底 |
.Alert-title | 粗体 + 16px 图标 + 8px 间距 |
.LoadingIndicator | 18 / 24 / 32px 圆环,750ms 匀速旋转,颜色取当前文字色 |
.Placeholder | 空状态:40px 上边距,1.4em 居中的 --muted-more-color |
.Tooltip | 12px 字号、最大 200px、深色底 rgba(0,0,0,.9)、7×12 内边距 |
.Pill | 4px 圆角的组合小标签(如「已赞 · 3」) |
6.7 分页与标签页
- 分页:
首 / 上 / 页码输入框 / 下 / 末五列,间距 8px;页码输入框最大 80px。
首页的讨论列表用「加载更多」而不是分页。 - 标签页 Tabs:15px 字号、
12px 8px内边距、底部 2px 主色下划线标记当前项。
6.8 帖子流
| 元素 | 规格 |
|---|---|
楼层 .Post | 上下 20px 内边距,圆角 4px |
| 头像列 | 桌面 85px(64px 头像 + 21px 空隙) |
| 用户名校验 | 14px / 700 / --heading-color |
| 时间 | 13px / --muted-color,点击展开「编号 / 完整时间 / IP / 永久链接」 |
| 正文 | 14px / 1.7(宽屏 15px) |
| 楼层分隔 | 1px --control-bg,右侧留出 20px |
| 时间间隔 | 12px 粗体大写 --muted-color,左缩进一个头像列 |
| 回复占位 | 2px 虚线圆角 10px,hover 时才显示边框 |
7. 图标
- 图标库:Font Awesome 7 Free(
vendor/flarum/font-awesome),本地托管。 - 统一写法:
<i class="icon fa-solid fa-reply Button-icon" aria-hidden="true"></i>
| 位置 | 尺寸 |
|---|---|
按钮内 .Button-icon | 14px |
图标按钮 .Button--icon | 16px |
| 侧边导航 | 15px |
| 下拉菜单项 | 16px(固定宽度,保证左对齐) |
| 徽章内 | 0.56 × 徽章尺寸 ≈ 12px |
| 帖子页「⋮」 | 16px(Button--icon) |
常用图标语义(保持全站一致):
| 语义 | 图标 |
|---|---|
| 回复 | fa-reply |
| 新讨论 | fa-edit(= fa-pen-to-square) |
| 更多操作 | fa-ellipsis-v / fa-ellipsis-h |
| 搜索 | fa-magnifying-glass |
| 通知 | fa-bell |
| 主题 | fa-sun / fa-moon / fa-circle-half-stroke |
| 置顶 | fa-thumbtack |
| 已读 | fa-check |
| 刷新 | fa-rotate |
| 标签 | fa-tag / fa-table-cells-large |
规则:
- 装饰性图标必须
aria-hidden="true",语义靠文字或aria-label。 - 不要用 emoji 代替图标(跨平台渲染不一致),emoji 只出现在用户内容里。
- 图标颜色跟随文字色,不单独设色;需要强调时改父级。
8. 动效
| 场景 | 时长 | 曲线 |
|---|---|---|
| 输入框边框/底色 | 150ms | ease |
| Tooltip 淡入 | 150ms | linear |
| 列表行 hover 底色 | 200ms | ease |
| 弹窗缩放出现 | 200ms | ease-out |
| 抽屉滑出 | 200ms | ease |
| 新楼层淡入 | 400ms | ease-in-out |
| 加载转圈 | 750ms | linear 无限 |
原则:
- 只为状态变化加动画,不为装饰加;列表 hover 是底色渐变,不做位移。
- 下拉菜单、通知面板不做进出动画——打开要即时。
- 动画时长不超过 400ms,超过会让人觉得卡。
- Flarum 源码里没有处理
prefers-reduced-motion;如果你做主题,建议补一条:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .01ms !important;
transition-duration: .01ms !important;
}
}
9. 可访问性
9.1 焦点环
所有可交互元素统一使用浏览器原生焦点环,只在键盘操作时出现:
outline: 5px auto Highlight;
outline: 5px auto -webkit-focus-ring-color;
.my-button { .add-keyboard-focus-ring(); } // 自己获得焦点
.my-input { .add-keyboard-focus-ring(":focus-within"); }
不要为了「好看」去掉 outline,也不要用 box-shadow 自制焦点环——高对比度模式下会消失。
9.2 语义与 ARIA
| 场景 | 做法 |
|---|---|
| 纯图标按钮 | 必须 aria-label(如「刷新」「全部标为已读」) |
| 装饰图标 | aria-hidden="true" |
| 图标徽章 | role="img" + aria-label(如「管理员」) |
| 下拉 | aria-haspopup="menu",当前项 aria-current="true" |
| 弹窗 | role="dialog" + aria-modal="true" + focus trap |
| 仅屏幕阅读器可见 | .sr-only;数字类信息再补一个 .visually-hidden 的完整描述 |
| 跳转 | 页面顶部 skip link,指向 #main-content |
9.3 颜色与对比
- 正文和背景的对比度非常充足(亮色 18.9:1)。
--muted-color约 4.2:1,适合次要文字,不建议放长段落。--muted-more-color只有 2.3~3:1,只用于元信息(时间、回复数说明)。- 彩色标签 / 徽章必须用 YIQ 判断前景色(源码里的
isDark()+text-contrast--*),
不要凭感觉选白字或黑字。
9.4 触控与键盘
- 主要操作的可点击区域不小于 36 × 36(图标按钮即 36px)。
- 手机端标题 + 信息区合成一个大点击区域(
.DiscussionListItem-main),右侧回复数是独立按钮。 - 弹窗打开时锁滚动、Esc 关闭、点击遮罩关闭。
- 下拉菜单支持 Esc / 失焦关闭;打开时焦点不跳走。
10. 改主题、写扩展
10.1 只改主色
后台 → 外观 → 主色 / 次色。等价于在 LESS 里改:
@config-primary-color: #2F6F5E;
@config-secondary-color: #2F6F5E;
重新编译后,链接色、按钮、遮罩、暗色背景、控制色会一起变。
不要逐个组件覆盖颜色。
10.2 覆盖单个 token
在「自定义 LESS」(后台)或主题扩展里:
:root {
--border-radius: 8px; /* 全局圆角 */
--post-font-size: 16px; /* 帖子正文字号 */
}
[data-theme^=dark] {
--primary-color: #7EA2C9; /* 暗色下用更亮的主色 */
}
10.3 扩展的 UI 约定
- 新增界面优先复用
.Button、.FormControl、.Dropdown、.Modal,不要另起一套。 - 需要往现有界面塞东西时,用
ItemList+ 优先级,而不是用 CSS 定位硬贴:
items.add('myButton', <Button>自定义</Button>, 50); // 数字越大越靠前
- 类名沿用 BEM 风格:
Block/Block-element/Block--modifier。 - 组件类名与 JS 组件名一致(
CommentPost、DiscussionHero),方便反查。
10.4 十条军规
- 不写死颜色,一律
var(--token)。 - 不覆盖
!important,先找对应的 token 或 mixin。 - 不改控件高度(36px),否则整页的垂直节奏会崩。
- 圆角只用 4px / 18px / 50% 三种。
- 层级用 z-index 表里的值,不写 9999。
- 一个区域只有一个主按钮。
- 纯图标按钮必须可被读出名字(
aria-label)。 - 新组件要有 hover / focus-visible / disabled / 暗色 四种状态。
- 装饰性动画一律可关(
prefers-reduced-motion)。 - 中文文案不硬编码在 JS 里,走翻译 key,方便换语言。
11. 附录:速查表
11.1 变量全表(常用)
/* 布局 */
--border-radius: 4px;
--header-height: 52px;
--header-height-phone: 46px;
--drawer-width: 270px;
--pane-width: 400px;
--avatar-column-width: 85px;
--post-padding: 20px;
--container-hd: 1200px;
--container-xl: 1300px;
--container-xxl: 1500px;
--discussion-content-max-width: 100ch;
--post-font-size: 14px; /* ≥1100px 时 15px */
/* 颜色(会随 data-theme 变化) */
--body-bg --body-bg-shaded --body-bg-light
--text-color --heading-color
--muted-color --muted-color-light --muted-more-color
--control-bg --control-bg-light --control-bg-shaded --control-color
--button-bg --button-bg-hover --button-bg-active
--button-primary-bg --button-primary-color
--link-color --primary-color --secondary-color
--shadow-color --overlay-bg --code-bg --code-color
--discussion-title-color --hero-bg --hero-color
--badge-bg --avatar-bg --tag-bg --tag-color
11.2 尺寸表
| 项 | 值 |
|---|---|
| 顶栏 | 52 / 46px |
| 控件高度 | 36px |
| 图标按钮 | 36 × 36 |
| 徽章 | 22px |
| 头像 | 24 / 32 / 36 / 48 / 64 |
| 标签字号 | 85% |
| 正文 | 14px / 1.7(HD 15px) |
| 界面文本 | 13px / 1.5 |
11.3 源码索引
下表路径默认省略前缀
vendor/flarum/core/;tags 扩展单独标注。
| 想知道 | 去看 |
|---|---|
| 颜色、尺寸、断点 | less/common/variables.less |
| 亮 / 暗 / 高对比度变量 | less/common/root.less |
| 按钮 | less/common/Button.less |
| 表单 | less/common/FormControl.less Form.less Checkbox.less |
| 下拉 | less/common/Dropdown.less |
| 弹窗 | less/common/Modal.less |
| 头像 / 徽章 | less/common/Avatar.less Badge.less |
| 首页 / 侧栏 | less/forum/IndexPage.less less/common/sideNav.less |
| 讨论列表 | less/forum/DiscussionList.less DiscussionListItem.less |
| 帖子 / 帖子流 | less/forum/Post.less PostStream.less Scrubber.less |
| 标签 | vendor/flarum/tags/less/common/TagLabel.less |
| 组件 DOM | js/src/**/components/*.tsx |
| 页面骨架 | views/frontend/forum.blade.php |
11.4 相关文件
- 本地 Demo:
site/index.html - 组件速查表:
site/styleguide.html - 重新编译样式:
build/compile.sh
本文档基于 Flarum 2.0.2 整理;不同版本之间数值可能略有调整,以源码为准。