Typora自定义CSS:代码块配色与标题自动编号实战
很多人用 Typora 写技术文档时间一长就对默认主题的代码块配色和标题序号不满意。Typora 的代码块配色默认跟着主题走但部分主题代码块在深色背景下对比度不够看久了费眼标题则只有层级没有序号长文档里经常分不清 2.3 和 3.2 到底谁是谁。其实这两件事都能通过自定义 CSS 解决核心就是往主题目录下的 user.css 里加一段样式。今天就把我整理好的实现代码、踩坑记录和排查流程一次说清楚适合所有想折腾 Typora 外观、又不太熟悉 CSS 细节的写作者照着抄就能用。1. 定制前需要知道的三件事1.1 Typora 的主题体系到底是怎么回事Typora 本质上是一个基于 Electron 的 Markdown 编辑器它的界面、渲染效果全部由 CSS 控制。你看到的大纲字号、引用块左边框颜色、代码块背景甚至目录缩进背后都是样式表。Typora 的主题文件以 .css 形式存放在主题目录里安装一个主题其实就是把对应的 CSS 文件丢进这个目录然后在偏好设置里切换。真正要改东西的时候大部分人不需要动主题文件本身而是新建或修改一个全局文件 user.css。这个文件会被所有主题加载优先级又比具体主题更高适合放自定义覆盖样式。我把代码块配色和标题序号全部放在 user.css 里换来换主题也不影响这两项设置。主题目录的位置每个系统不一样Windows 上是C:\Users\你的用户名\AppData\Roaming\Typora\themesmacOS 上是~/Library/Application Support/Typora/themes/Linux 一般是~/.config/Typora/themes/。最稳的方式是在 Typora 的偏好设置里找到“外观”选项卡点“打开主题文件夹”它会直接弹出这个目录省得手敲路径。1.2 代码块配色由谁控制Typora 的代码块分两层外面一层是.md-fences负责背景、边框、圆角、内边距这些容器样式里面一层是.cm-s-inner和一堆.cm-*开头的类负责语法标记的颜色。cm这个前缀来自 CodeMirrorTypora 内部用 CodeMirror 做代码编辑和实时高亮所以关键字、字符串、注释、函数名这些 token 都有专门的颜色类。很多人改代码块配色失败就是因为只改了.md-fences的背景没去碰 token 颜色结果代码文字还是原来那套看起来当然不对劲。改代码块配色必须两条线一起走先确保容器背景是你想要的颜色再逐个覆盖 token 颜色。这里有个很有意思的点因为每个主题自带的 token 颜色不同所以你的覆盖样式偶尔会被主题里的!important规则挡住。遇到这种情况要么在自己的选择器后面也补一个!important要么把选择器写得再具体一点比如从.cm-s-inner .cm-keyword升级成#write .md-fences .cm-s-inner .cm-keyword基本就能压住。1.3 标题序号的“正确生成姿势”标题自动加序号最容易想到的办法是在源 Markdown 里手动输入“1.”“1.1”这类前缀。这么做的确直观但后期维护非常痛苦中间插入一个小节后面所有序号都要手改一遍而且导出 PDF 之后序号直接凝固成文本想整体换格式都难。更靠谱的做法是利用 CSS 自带的计数器机制也就是counter-reset和counter-increment。原理不复杂定义几个计数器每遇到一个 h1 就给 h1 计数器加 1每遇到一个 h2 就给 h2 计数器加 1然后通过伪元素::before把计数器的值显示在标题前面。这样写正文时完全不用管序号插入实例题、调整结构标题编号自动跟着变。这套方案唯一的限制是需要理解 CSS 选择器和计数器的作用范围。好在代码量不大核心块十几行就能搞定而且 Typora 对#write h1这类选择器支持得非常好正文内的标题渲染和导出都能同步生效。2. 代码块配色实现代码与调色思路2.1 一套可直接粘贴的深色代码块配色下面是一套我从日常使用中整理出来的深色代码块配色整体走低饱和路线长时间看不容易疲劳。它主要模仿常见编辑器暗色主题里那种“墨蓝底、柔和高亮”的感觉粘贴到 user.css 就能用。/* 代码块配色开始 */ .md-fences { background-color: #1e1e2e !important; border: 1px solid #313244 !important; border-radius: 8px; font-size: 14px; line-height: 1.6; } .md-fences .CodeMirror { background-color: #1e1e2e !important; } .md-fences .CodeMirror .CodeMirror-line, .cm-s-inner { color: #cdd6f4 !important; } /* 关键字if、for、class 等 */ .cm-s-inner .cm-keyword { color: #cba6f7 !important; font-weight: 600; } /* 字符串 */ .cm-s-inner .cm-string { color: #a6e3a1 !important; } /* 注释 */ .cm-s-inner .cm-comment { color: #6c7086 !important; font-style: italic; } /* 数字 */ .cm-s-inner .cm-number { color: #fab387 !important; } /* 函数名、方法名 */ .cm-s-inner .cm-def { color: #89b4fa !important; } /* 变量名 */ .cm-s-inner .cm-variable { color: #f38ba8 !important; } /* 标签名主要用在 HTML/XML 里 */ .cm-s-inner .cm-tag { color: #f38ba8 !important; } /* 标签属性名 */ .cm-s-inner .cm-attribute { color: #fab387 !important; } /* 运算符、等号、加减乘除 */ .cm-s-inner .cm-operator { color: #94e2d5 !important; } /* 内置函数、真值、空值等 */ .cm-s-inner .cm-builtin, .cm-s-inner .cm-atom { color: #cba6f7 !important; } /* 代码块配色结束 */这套配色覆盖了日常写代码最常用的几类语法标记。如果你用的是浅色主题只需要把背景色改成#f5f5f7、边框改成#e0e0e0再把文字主色换成#333token 颜色换成更深的色系就可以。2.2 这些选择器分别控制什么我直接整理成一张对照表方便你排查具体某个颜色出自哪里。选择器控制内容.md-fences代码块整体背景、边框、圆角、内边距、字号.md-fences .CodeMirror代码编辑区背景通常和.md-fences背景保持一致.cm-s-innerCodeMirror 主题容器内的默认文字颜色.cm-keyword控制关键字比如 if、else、for、class、def.cm-string字符串内容包括单引号、双引号包裹的部分.cm-comment注释文字.cm-number数字常量.cm-def函数或方法定义处的名称.cm-variable变量名不同语言表现差异比较大.cm-tagHTML、XML 里的标签名.cm-attribute标签上的属性名.cm-operator运算符比如 - * / 之类.cm-builtin语言内置对象或常量比如 True、False、None有一个容易被忽略的细节不同语言在 CodeMirror 里解析出的 token 类型并不完全一样。同样的变量名在 Python 里可能是.cm-variable在 JavaScript 里也可能是.cm-variable-2或者.cm-property。改完主配色后如果发现某个语言下部分文字颜色没变那就需要打开开发者工具选中那段代码看它实际用的是哪个类再对症下药。2.3 如何按自己审美调整配色调色的时候不要凭空想我推荐一个非常实用的方法先找一张你喜欢的主题图或者直接看某个编辑器的默认主题截图然后把色值抄下来逐个替换到 CSS 里。比如你喜欢高对比度风格那关键字用亮紫#bd93f9字符串用亮绿#50fa7b注释用灰紫#6272a4背景直接上接近纯黑的#282a36出来的效果就是经典的午夜系风格。文字颜色和背景的对比度一定要测。我自己的经验是主体文字和背景的亮度差至少要大于 30%不然长时间阅读会累注释可以稍微暗一些但也不能暗到完全看不清。可读性是第一位的配色好看但看不清完全没必要。如果你不想自己一个颜色一个颜色试还有一个取巧办法直接把别的开源主题的配色块复制过来。比如找一套已经有名的 Typora 主题打开它的 CSS 文件复制里面.cm-keyword、.cm-string相关段落再改成自己的背景色微调几下就能用。2.4 别忘了光标、选中和当前行很多人改完代码块背景和 token 颜色之后发现光标颜色还停留在默认主题的深蓝色或者选中代码时的高亮色和背景完全融为一体体验很差。这三处虽然不影响截图效果但日常写代码时最影响心情。/* 光标颜色 */ .md-fences .CodeMirror-cursor { border-left: 2px solid #89b4fa !important; } /* 选中代码的高亮背景 */ .md-fences .CodeMirror ::selection { background-color: #313244 !important; } /* 当前激活行 */ .md-fences .CodeMirror-activeline-background { background-color: rgba(137, 180, 250, 0.08) !important; }当前激活行的背景建议用半透明色透明度控制在 0.06 到 0.12 之间既能看清当前在第几行又不会干扰代码本身的阅读。光标颜色选和主题强调色一致即可浅色主题里用深蓝色也没问题。另外如果你经常写长代码块建议给代码块加一个横向滚动效果的过渡但 Typora 原生不太支持精细控制我能给的实用建议是不要在 CSS 里强行改宽度保持默认最稳。3. 标题自动编号用 CSS 计数器做一套“自动编号器”3.1 计数器原理CSS 计数器理解起来并不难。你可以把它想象成一个大仓库里的电子计数器counter-reset负责把某个编号归零counter-increment负责让编号加一content负责把编号显示出来。它和手动写序号最大的区别在于编号是动态计算的文档结构一变编号跟着变。在 Typora 里正文标题区域的最外层容器是#write所有标题都在#write下面。所以我们要在#write上统一声明一组计数器初始值然后每个标题级别分别负责递增和清零。比如遇到 h1h1 计数器加 1同时把 h2、h3 这些下级计数器清零遇到 h2h2 计数器加 1同时把 h3 清零。这样就能保证文档任意位置插入标题后续编号自动顺延。实际编写时有一个关键点计数器的递增动作和显示动作最好不要写死在同一个地方的属性上而是分别写在标题本身和标题伪元素上。比如h1 { counter-increment: h1 }负责计数h1::before { content: counter(h1) }负责显示这样整体逻辑更清晰也方便把序号和标题文字之间的间距单独控制。3.2 标题编号的完整代码下面这段代码实现了从 h1 到 h6 的分层编号显示格式是经典的多级序号h1 显示“1、”h2 显示“1.1”h3 显示“1.1.1”以此类推。/* 标题自动编号开始 */ #write { counter-reset: h1; } #write h1 { counter-increment: h1; counter-reset: h2; } #write h2 { counter-increment: h2; counter-reset: h3; } #write h3 { counter-increment: h3; counter-reset: h4; } #write h4 { counter-increment: h4; counter-reset: h5; } #write h5 { counter-increment: h5; counter-reset: h6; } #write h6 { counter-increment: h6; } /* h1 序号1、 */ #write h1::before { content: counter(h1) 、; color: #89b4fa; margin-right: 0.3em; } /* h2 序号1.1 */ #write h2::before { content: counter(h1) . counter(h2) ; color: #89b4fa; margin-right: 0.3em; } /* h3 序号1.1.1 */ #write h3::before { content: counter(h1) . counter(h2) . counter(h3) ; color: #89b4fa; margin-right: 0.3em; } /* h4 序号1.1.1.1 */ #write h4::before { content: counter(h1) . counter(h2) . counter(h3) . counter(h4) ; color: #89b4fa; margin-right: 0.3em; } /* h5 序号1.1.1.1.1 */ #write h5::before { content: counter(h1) . counter(h2) . counter(h3) . counter(h4) . counter(h5) ; color: #89b4fa; margin-right: 0.3em; } /* h6 序号1.1.1.1.1.1 */ #write h6::before { content: counter(h1) . counter(h2) . counter(h3) . counter(h4) . counter(h5) . counter(h6) ; color: #89b4fa; margin-right: 0.3em; } /* 标题自动编号结束 */代码里的counter(h1)这种写法本质是把当前计数器值转成文本多个计数器之间用.拼接就形成了多级编号。h1 用的是“、”是因为很多人习惯在一级标题后面用顿号如果你更喜欢“1. 标题”这种格式把content: counter(h1) 、改成content: counter(h1) . 就行。还要提醒一点::before默认是行内元素所以序号会紧贴在标题文字前面margin-right 控制的是序号和标题文字之间的空隙。这个间距我习惯放在 0.3em 左右太小容易挤太大又显得散。3.3 各层级序号格式调整真正写文章的时候不同场景对序号格式的需求不太一样。想用“第一章、第一节、1.1”这种中文风格就把 h1 的content改成counter(h1, cjk-ideographic) 、CSS 里的cjk-ideographic计数器样式会把阿拉伯数字转成中文数字效果类似“一、”但注意它不一定能转出“十”“百”以上特别复杂的数字。如果你的文档没有 h1直接从 h2 开始那最外层编号就会从 0 开始因为 h2 自身只负责递增它的计数器而最外层的 h1 计数器一直没被递增。解决办法很简单在#write h2::before里只显示counter(h2)不拼 h1 那一层格式自然变成1.、2.。同理如果你的标题只用到三级后面不用写 h4 到 h6 的映射。这里我放两种常见格式直接替换对应内容即可/* 格式 A1. 标题 */ #write h1::before { content: counter(h1) . ; } /* 格式 B第一章标题 */ #write h1::before { content: 第 counter(h1, cjk-ideographic) 章 ; }两种格式的取舍完全看文档类型。写技术方案、开发文档我推荐格式 A清晰直白写教程、讲义类内容可以试试格式 B更有书的质感。我自己的习惯是技术文档一律用格式 A因为代码抽检、截图发文时更通用。3.4 复制粘贴与目录的问题这套 CSS 方案一个绕不开的副作用是当你选中标题文字复制到别的地方时序号会跟着复制走。因为序号是伪元素渲染出来的在视觉上和标题文字混在一起复制的时候自然一起带上了。如果你经常要把标题复制进表格、邮件或者聊天工具这一点需要提前适应。另外插入 [TOC] 生成目录时目录默认会不会显示这些序号取决于 Typora 版本和主题。多数情况下目录里的标题是独立渲染的不会直接用#write h1::before这套规则所以目录里不会出现编号。想要目录也有编号可以把#write h1::before这类选择器再加一套针对.md-toc的副本但成本高、收益低我一般不会这么做因为从目录直接跳转更注重的是内容和可点击性。4. 一套组合拳完整 user.css 与安装步骤4.1 组合示例我把前面的代码块配色和标题自动编号合并成一个完整的 user.css。这样一份文件就能让你从默认主题升级成“深色代码块 自动标题编号”的写作环境。/* Typora 自定义样式 功能代码块深色配色 标题自动编号 放置位置Typora 主题目录下的 user.css */ /* ---------- 一、代码块配色 ---------- */ .md-fences { background-color: #1e1e2e !important; border: 1px solid #313244 !important; border-radius: 8px; font-size: 14px; line-height: 1.6; } .md-fences .CodeMirror { background-color: #1e1e2e !important; } .md-fences .CodeMirror .CodeMirror-line, .cm-s-inner { color: #cdd6f4 !important; } .cm-s-inner .cm-keyword { color: #cba6f7 !important; font-weight: 600; } .cm-s-inner .cm-string { color: #a6e3a1 !important; } .cm-s-inner .cm-comment { color: #6c7086 !important; font-style: italic; } .cm-s-inner .cm-number { color: #fab387 !important; } .cm-s-inner .cm-def { color: #89b4fa !important; } .cm-s-inner .cm-variable { color: #f38ba8 !important; } .cm-s-inner .cm-tag { color: #f38ba8 !important; } .cm-s-inner .cm-attribute { color: #fab387 !important; } .cm-s-inner .cm-operator { color: #94e2d5 !important; } .cm-s-inner .cm-builtin, .cm-s-inner .cm-atom { color: #cba6f7 !important; } /* 光标、选中、当前行 */ .md-fences .CodeMirror-cursor { border-left: 2px solid #89b4fa !important; } .md-fences .CodeMirror ::selection { background-color: #313244 !important; } .md-fences .CodeMirror-activeline-background { background-color: rgba(137, 180, 250, 0.08) !important; } /* ---------- 二、标题自动编号 ---------- */ #write { counter-reset: h1; } #write h1 { counter-increment: h1; counter-reset: h2; } #write h2 { counter-increment: h2; counter-reset: h3; } #write h3 { counter-increment: h3; counter-reset: h4; } #write h4 { counter-increment: h4; counter-reset: h5; } #write h5 { counter-increment: h5; counter-reset: h6; } #write h6 { counter-increment: h6; } #write h1::before { content: counter(h1) 、; color: #89b4fa; margin-right: 0.3em; } #write h2::before { content: counter(h1) . counter(h2) ; color: #89b4fa; margin-right: 0.3em; } #write h3::before { content: counter(h1) . counter(h2) . counter(h3) ; color: #89b4fa; margin-right: 0.3em; } #write h4::before { content: counter(h1) . counter(h2) . counter(h3) . counter(h4) ; color: #89b4fa; margin-right: 0.3em; } #write h5::before { content: counter(h1) . counter(h2) . counter(h3) . counter(h4) . counter(h5) ; color: #89b4fa; margin-right: 0.3em; } #write h6::before { content: counter(h1) . counter(h2) . counter(h3) . counter(h4) . counter(h5) . counter(h6) ; color: #89b4fa; margin-right: 0.3em; }这份文件的注释我保留得比较完整方便你以后想改某块颜色时快速定位。使用过程中如果发现某些代码语言高亮不生效多半是 token 类型没覆盖到看第 2.2 小节的对照表单独补一个选择器就行。4.2 安装与加载步骤安装流程其实就五步打开 Typora进入“文件 - 偏好设置”。切到“外观”选项卡点“打开主题文件夹”。在主题目录下找到user.css没有就新建一个。把上面的完整代码粘贴进去保存。回到 Typora点击“外观”选项卡里的“重新加载主题”或者直接按CtrlRWindows/Linux刷新窗口。刷新之后打开一个包含多级标题和代码块的文档立刻就能看到效果。如果你的电脑上 Typora 没有响应刷新快捷键重启一次应用也能加载新样式。如果是 Mac快捷键是CommandR。刷新比重启快得多我平时调 CSS 都是几十秒一轮改完刷新看效果不对再改再刷新。4.3 导出 PDF/HTML 时如何保住样式代码块配色和标题编号在编辑器里生效只是第一步很多人最后导出 PDF 发给同事或打印时发现样式丢了或者颜色完全不同。这里有个原则要记住Typora 导出时正常情况下会复用编辑器里的 CSS但不同版本、不同自定义主题处理方式有差异。为了保证导出也能保持一致的样式我的习惯是写 CSS 时不加media screen这种屏幕限定。如果你看到代码里有类似的媒体查询导出时可能会被排除掉导致样式不生效。另外导出 PDF 前建议先“导出 HTML”然后在浏览器里预览一遍确认代码块配色和标题编号都正常后再考虑 PDF。HTML 是最接近真实渲染效果的格式能在网页里正常显示再转 PDF 基本就稳妥了。如果导出 HTML 后发现代码高亮用的是.token或.hljs开头的类名那说明 Typora 在导出时换了另一套高亮类名你需要在 user.css 里补一套对应选择器把编辑器和导出的样式一起覆盖。5. 常见问题与排查技巧实录5.1 改完没生效怎么查这是最常遇到的问题通常不是代码错了而是没加载或覆盖优先级不够。第一步确认文件位置和文件名。Typora 读取的是主题目录下的user.css名字不能写错后缀必须是小写的.css我见过有人建成了user.css.txt这种文件 Typora 根本不会读取。第二步确认已经刷新。修改 user.css 后不会自动生效必须手动刷新或重启。刷新后如果还不行打开开发者工具找到代码块对应的元素看看样式面板里到底哪条规则覆盖了你的设置。如果系统自动生成的规则带了!important你自己的规则也要带上否则优先级低就会被压住。第三步检查是不是选择器写得太窄。比如你在 user.css 里写了.cm-keyword { color: red }但实际页面上这个关键词高亮类其实是.cm-variable-2那自然没效果。这时需要打开开发者工具选中那个具体单词查看它实际绑定了哪些类再调整选择器。5.2 标题编号变成 0、重复或错乱标题编号出现 0最典型的问题是计数器没有递增。比如你自己定义了counter-reset: h1但忘了写#write h1 { counter-increment: h1 }那counter(h1)永远只有初始值 0。这种情况补上递增规则就能解决。出现重复编号通常是多套计数器规则同时生效。例如某个主题本身已经自带标题自动编号你又加了一套两套编号叠加显示视觉上就会像“1. 1.1”或者两个相同序号前后出现。处理方法是去主题自带 CSS 里找到相关规则把它们 disable 掉或者只保留你自己的这套。还有一种情况是在源 Markdown 的标题文字里手工写了序号比如标题文字是“3.2 环境配置”这时 CSS 序号会渲染成“3.2 3.2 环境配置”看着就像重复了。这种问题只能靠移除标题里的手动序号解决。5.3 代码块配错色只改对了一半如果你发现代码块背景变深了但关键字、字符串这些还是原来的颜色说明你的 token 颜色规则没有覆盖到主题自带的 token 规则。解决办法很简单在每条 token 规则后面加!important并适当提高选择器优先级。还有一种情况是同一个 token 在代码的不同位置显示不同颜色。CodeMirror 的解析粒度很细比如同样是变量函数参数可能是.cm-variable-2类名可能是.cm-variable-3甚至有的语言里变量名会被解析成.cm-property。只写.cm-variable一类的规则覆盖不了这么全所以遇到局部颜色不理想先别再猜直接用开发者工具看具体类名然后针对性加规则。5.4 我踩过的几个坑和最终维护习惯最后说几条我长期维护 user.css 后才总结出来的经验。第一所有自定义规则尽量都写在一个文件里别拆到多个 css 文件否则自己都容易忘记哪条规则在哪里生效。第二给每一段加注释标明控制什么功能、在什么时间改过后面回来调整能省大量时间。第三代码块配色改动尽量用十六进制颜色值不要用颜色名比如red这种颜色名在不同下的解释有差异十六进制更明确。我最常遇到的是导出 PDF 时的样式差异后来干脆给自己定了一条规矩每次改完样式先导出一次 HTML 验证确认没问题再继续写文档。因为很多样式只在实时编辑环境里生效导出后能不能保持完全改观。这样坚持下来文档交付阶段基本不会在代码块配色上翻车。这套“代码块配色 标题自动编号”的组合我已经用了很长一段时间它最大的价值不是让我有了一个好看的 Typora而是让我在写长文档时不用分心去维护序号代码块阅读也有了稳定舒适的视觉体验。如果你也想折腾先从完整 user.css 开始跑通流程之后再慢慢调成自己喜欢的样子。