Textual 主题系统完全指南:从内置主题到自定义主题设计【免费下载链接】textualThe lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.项目地址: https://gitcode.com/gh_mirrors/te/textualTextual 内置了数十套主题,并允许开发者通过一个简单的 Python 对象定义自己的主题,从而为整个应用提供统一的 CSS 颜色变量。本文将围绕 docs/guide/design.md 这份官方指南,结合 src/textual/theme.py 与 src/textual/design.py 的源码实现,系统讲解主题的切换、注册、变量生成机制,以及如何让应用文本在任何背景下保持可读,帮助你在终端应用中快速落地一套可随时切换的配色体系。主题是什么:CSS 变量的来源在 Textual 中,主题(Theme)本质上是一个把变量名映射到颜色值的 Python 对象。应用编写 CSS 时并不直接写死颜色,而是引用$primary、$foreground这类以$开头的变量。切换主题时,Textual 会重新计算这些变量的值,应用界面随之整体换色,无需改动任何 CSS。源码层面,主题由Themedataclass 定义(src/textual/theme.py),字段如下:字段类型默认值说明namestr必填主题名称,注册后通过App.theme name激活primarystr必填主色,主题唯一必填的颜色secondarystr \| NoneNone辅助色,缺省时回退为primarywarning/error/success/accentstr \| NoneNone语义色,缺省时回退为primary或secondaryforegroundstr \| NoneNone默认文本色,缺省时取背景色的反色background/surface/panel/booststr \| NoneNone背景层级色,缺省时使用框架默认值darkboolTrue是否为暗色主题,影响变量生成与内置控件外观luminosity_spreadfloat0.15色阶(lighten/darken)的明度扩散幅度text_alphafloat0.95文本透明度基准variablesdict[str, str]{}对具体变量的覆盖(见下文额外变量)ansiboolFalse为True时生成原生 ANSI 颜色主题从源码可以看出,Textual 内置了大量主题。BUILTIN_THEMES字典(src/textual/theme.py)中定义了textual-dark、textual-light、nord、gruvbox、catppuccin-mocha、dracula、tokyo-night、monokai、flexoki、catppuccin-latte/frappe/macchiato、solarized-light/dark、rose-pine系列、atom-one-dark/light,以及ansi-dark、ansi-light等约 20 套主题,它们在App.__init__中被逐个注册(src/textual/app.py),因此开箱即用。切换主题:命令面板与代码两种方式通过命令面板(Command Palette)主题可以在运行时通过命令面板切换(默认快捷键ctrlp)。命令面板中会列出所有可用主题,选中即立即应用。这背后由ThemeProvider实现(src/textual/theme.py):它以app.available_themes为数据源生成命令条目,支持模糊搜索匹配主题名,执行时调用app.theme name。通过代码设置也可以在代码中编程式地切换主题,给App.theme赋值即可:class MyApp(App): def on_mount(self) - None: self.theme nordApp.theme是一个带校验的 reactive 属性。从源码看,赋值时会先经过_validate_theme(src/textual/app.py):如果主题名未注册,会抛出InvalidThemeError,提示Call App.register_theme before setting the App.theme attribute;校验通过后由_watch_theme触发应用刷新(src/textual/app.py)。因此,主题必须先注册才能使用。仓库中的示例应用 docs/examples/themes/todo_app.py 演示了运行时循环切换主题的完整做法:它通过Binding(ctrlt, cycle_theme, Cycle theme)绑定快捷键,在action_cycle_theme中调用self.theme next(self.THEMES)依次在nord、gruvbox、tokyo-night、textual-dark、solarized-light间切换,并在on_mount中执行一次,运行起来即可看到同一应用在不同主题下的外观变化。注册自定义主题主题的注册流程是:先构造Theme对象,再在App.on_mount中调用App.register_theme。文档给出的完整示例:from textual.theme import Theme arctic_theme Theme( namearctic, primary#88C0D0, secondary#81A1C1, accent#B48EAD, foreground#D8DEE9, background#2E3440, success#A3BE8C, warning#EBCB8B, error#BF616A, surface#3B4252, panel#434C5E, darkTrue, variables{ block-cursor-text-style: none, footer-key-foreground: #88C0D0, input-selection-background: #81a1c1 35%, }, )from textual.app import App class MyApp(App): def on_mount(self) - None: # Register the theme, making it available to the app (and command palette) self.register_theme(arctic_theme) # Set the apps theme. When this line runs, the app immediately refreshes. self.theme arctic关于注册机制,源码提供了几个值得注意的细节:register_theme(src/textual/app.py)将主题存入_registered_themes字典,同名主题会覆盖旧主题;unregister_theme可按名称移除已注册主题(src/textual/app.py);available_themes属性返回内置主题 已注册主题的合并字典,命令面板据此列出全部主题(src/textual/app.py);get_theme支持逗号分隔的主题名列表,依次返回第一个可用的主题,可用于优雅降级(src/textual/app.py);若App.theme指向的主题不可用,current_theme会自动回退到textual-dark(src/textual/app.py)。主题变量与 CSS 中的用法主题提供最多 11 种基础色,Textual 会基于它们生成一整套 CSS 变量。例如textual-dark将primary定义为首色,主题中直接以$primary引用:MyWidget { background: $primary; color: $foreground; }切换主题后,这些变量的值会更新为当前主题的对应颜色,MyWidget的颜色随之自动更新。变量如何生成?关键在于App.get_css_variables(src/textual/app.py):它把当前主题转换成ColorSystem并调用generate()产出基础变量映射,再依次合并theme.variables与App.get_theme_variable_defaults()的结果,最终成为应用可用的完整 CSS 变量表。也就是说,变量值的优先级从高到低是:主题variables覆盖 框架生成的默认值 App.get_theme_variable_defaults提供的兜底值(合并顺序见代码中combined_variables {**theme_variables, **variables},其中variables已包含主题覆盖)。11 种基础颜色(Base colors)定义主题时只有primary是必填的,其他基础色未提供时 Textual 会尝试自动生成(缺省回退逻辑见 src/textual/design.py,如secondary secondary or primary、error error or secondary)。下表列出了 11 种基础色在 CSS 中的变量名及默认用途(整理自官方文档):颜色说明$primary主色,可视为品牌色。通常用于标题,以及需要强烈强调的背景。$secondary备选品牌色,用途与$primary类似,用于需要与主色区分的场景。$foreground默认文本色,应保证在$background、$surface、$panel上清晰可读。$background无内容区域的背景色,也是屏幕的默认背景色。$surface控件的默认背景色,通常叠在$background之上。$panel用于区分 UI 中与主内容不同的部分,Textual 自身使用得比较克制。$boost带透明度的颜色,可在背景上制造层次感。$warning表示警告,通常作背景色;对应的前景色可用$text-warning。$error表示错误,通常作背景色;对应的前景色可用$text-error。$success表示成功,通常作背景色;对应的前景色可用$text-success。$accent用于少量、克制的视觉强调,通常与$primary、$secondary形成对比。色阶:Shades 与明暗变体对每一种基础色,Textual 都会生成 3 档加深、3 档变浅的色阶:在变量名后加-lighten-1、-lighten-2、-lighten-3得到更浅的色阶(3最浅);加-darken-1、-darken-2、-darken-3得到更深的色阶(3最深)。例如$secondary-darken-1是略微加深的$secondary,$error-lighten-3是非常浅的$error色。从源码看,色阶的生成由ColorSystem._generate中的luminosity_range完成(src/textual/design.py):明度步长由luminosity_spread决定(默认0.15,即每一档变化0.15 / 2),对每个颜色依次调用color.lighten(delta)计算。这也解释了Theme构造函数中luminosity_spread参数的作用——它控制整个主题明暗色阶的扩散幅度。所有基础色及其色阶的完整清单由ColorSystem.shades属性枚举(src/textual/design.py),其名目包含primary、secondary、background、surface、panel、boost、warning、error、success、accent及primary-background、secondary-background等。亮色与暗色主题主题通过Theme构造函数的dark参数声明为亮色或暗色。这一标志会显著影响变量的生成方式,内置控件也会读取该值来调整自身外观。在ColorSystem._generate中(src/textual/design.py),当darkTrue时,未指定的background、surface分别回退到暗色默认值#121212与#1e1e1e;亮色主题则回退到#efefef与#f5f5f5(常量定义见 src/textual/design.py)。foreground未提供时取background.inverse。此外,对于暗色主题,primary-background、secondary-background这类背景型色阶会采用与背景混合的专属计算路径(DARK_SHADES分支),以保证深色背景下标题背景不刺眼。因此,定义一个darkFalse的亮色主题时,通常还应显式提供较浅的background、surface、panel,参考内置的solarized-light、textual-light、atom-one-light等定义(src/textual/theme.py)。文本颜色:默认、弱化与禁用主题中文本的默认颜色是$foreground,应确保其在$background、$surface、$panel上清晰可读。除此之外,还有两种低优先级文本色:$foreground-muted:用于重要性较低的文本(如副标题、补充信息),默认是$foreground叠加 60% 透明度;$foreground-disabled:用于禁用状态的文本(如不可选中的菜单项),默认叠加 38% 透明度。生成逻辑见 src/textual/design.py。文本颜色可以通过 color 这个 CSS 属性设置。确保任意背景下的文本可读性当控件背景色不可预测时,Textual 提供了三个专用于保证可读性的变量:$text:根据文本所在背景自动选择带轻微透明度的黑色或白色,以保证对比度(默认值为auto 87%,即系统自动判定明暗);$text-muted:略微淡化的文本色,适合副标题等次要信息(默认auto 60%);$text-disabled:明显淡化的文本色,表示已禁用状态(默认auto 38%)。这三个变量的默认值同样定义在 src/textual/design.py。彩色文本(Colored text)从基础色还会生成 6 种彩色文本变量,它们被保证在$background、$surface、$panel背景上清晰可读:$text-primary$text-secondary$text-accent$text-warning$text-error$text-success例如$text-primary是经过着色处理、确保可读性的$primary变体。从源码看,彩色文本的生成方式是取背景的对比文本色,再与带 66% 透明度的基础色做 tint 混合(src/textual/design.py),从而兼顾有彩色与可读两个目标。仓库示例 docs/examples/themes/colored_text.py 用 6 行 CSS 演示了所有彩色文本变量:它为每种颜色生成一个类.text-{color} { color: $text-{color}; },并渲染 6 个标签展示$text-primary、$text-secondary等效果。文档还特别强调,这些彩色文本在柔和色(muted)背景的控件上作为前景色时同样保证可读。柔和色(Muted colors)柔和色由基础色与$background按70% 混合生成。例如$primary-muted就是柔化版的$primary。Textual 保证生成的彩色文本在对应柔和色背景上清晰可读,即$text-primary文本放在$primary-muted背景上依然清晰。可用的柔和色变量为:$primary-muted$secondary-muted$accent-muted$warning-muted$error-muted$success-muted混合计算位于 src/textual/design.py,即primary.blend(background, 0.7)。示例 docs/examples/themes/muted_backgrounds.py 用background: ${color}-muted; color: $text-{color};的 CSS 展示了 6 组彩色文本 柔和背景的组合效果。上面示例 docs/examples/themes/todo_app.py 的 CSS 也大量使用了这套机制,例如#overdue { color: $text-error; background: $error-muted; }、#done { color: $text-success; background: $success-muted; }。额外变量:内置控件配色定制Textual 用基础色作为默认值,为整个框架维护了大量额外变量;这些变量可以通过Theme构造函数的variables参数覆盖,连$primary-muted这类派生变量也可以覆盖。文档中以 Gruvbox 主题为例,将块光标(如OptionList中使用的光标)的前景色覆盖为$foreground:Theme( namegruvbox, primary#85A598, secondary#A89A85, warning#fabd2f, error#fb4934, success#b8bb26, accent#fabd2f, foreground#fbf1c7, background#282828, surface#3c3836, panel#504945, darkTrue, variables{ block-cursor-foreground: #fbf1c7, input-selection-background: #689d6a40, }, )内置主题对variables的使用在 src/textual/theme.py 中随处可见,例如nord覆盖了block-cursor-background、footer-key-foreground、button-color-foreground等。下面是文档整理的完整变量清单及其默认值(在ColorSystem._generate中以get(name, default)方式逐项产出,见 src/textual/design.py):边框(Border)变量用途默认值$border带边框且聚焦控件的边框色$primary$border-blurred未聚焦控件的边框色轻微加深的$surface光标(Cursor)变量用途默认值$block-cursor-foreground块光标(如 OptionList)的文本色$text$block-cursor-background块光标的背景色$primary$block-cursor-text-style块光标的文本样式bold$block-cursor-blurred-foreground未聚焦块光标的文本色$text$block-cursor-blurred-background未聚焦块光标的背景色30% 透明度的$primary$block-cursor-blurred-text-style未聚焦块光标的文本样式none$block-hover-background悬停块时的背景色5% 透明度的$boost输入(Input)变量用途默认值$input-cursor-background输入光标的背景色$foreground$input-cursor-foreground输入光标的文本色$background$input-cursor-text-style输入光标的文本样式none$input-selection-background选中文本的背景色40% 透明度的$primary-lighten-1滚动条(Scrollbar)变量用途默认值$scrollbar滚动条颜色$panel$scrollbar-hover悬停时的滚动条颜色$panel-lighten-1$scrollbar-active拖动(激活)时的滚动条颜色$panel-lighten-2$scrollbar-background滚动条轨道颜色$background-darken-1$scrollbar-corner-color滚动条角落颜色同$scrollbar-background$scrollbar-background-hover悬停滚动条区域时轨道颜色同$scrollbar-background$scrollbar-background-active滚动条激活时轨道颜色同$scrollbar-background链接(Links)变量用途默认值$link-background链接的背景色initial$link-background-hover悬停时链接的背景色$primary$link-color链接的文本色$text$link-style链接的文本样式underline$link-color-hover悬停时链接的文本色$text$link-style-hover悬停时链接的文本样式bold not underline页脚(Footer)变量用途默认值$footer-foreground页脚文本色$foreground$footer-background页脚背景色$panel$footer-key-foreground页脚中按键绑定的文本色$accent$footer-key-background页脚中按键绑定的背景色transparent$footer-description-foreground页脚中描述的文本色$foreground$footer-description-background页脚中描述的背景色transparent$footer-item-background页脚条目的背景色transparent按钮(Button)变量用途默认值$button-foreground标准按钮的前景色$foreground$button-color-foreground彩色按钮的前景色$text$button-focus-text-style聚焦按钮的文本样式bold reverse此外,ColorSystem._generate还产出$surface-active、$input-selection-foreground、$markdown-h1~$markdown-h6系列(标题颜色/背景/样式)、$screen-selection-background/foreground(整屏选择)等变量,均可通过variables覆盖。应用专属变量:get_theme_variable_defaults框架变量之外,应用常常需要暴露自己业务相关的 CSS 变量。做法是重写App.get_theme_variable_defaults,让它返回一个变量名 - 默认值的字典:class MyApp(App): def get_theme_variable_defaults(self) - dict[str, str]: return { my-widget-accent: #ff8800, # 值可以是任意合法 CSS 值,如 red 50%、auto 90%、#ff0000、rgb(255, 0, 0) 等 }优先级规则(文档明确说明,且与get_css_variables的合并逻辑一致):如果某个变量同时出现在该字典和主题的variables中,主题中的值优先生效。换句话说,get_theme_variable_defaults提供的是主题未覆盖时的兜底默认值。需要注意的是,如果在 CSS 中引用了既不在该字典、也不在主题中的变量,应用启动解析 CSS 时会直接失败(相关说明见 src/textual/app.py 的 docstring)。预览主题与颜色:textual colors 命令在命令行直接运行以下命令,即可预览颜色系统定义的全部颜色:textual colors预览界面会并排展示亮色(light)与暗色(dark)两套色板,列出每个基础色及其全部明暗色阶。在预览中,同样可以通过命令面板(ctrlp)切换主题,即时查看不同主题下基础变量与色阶的变化。该预览渲染逻辑对应 src/textual/design.py 的show_design函数:它按ColorSystem.shades枚举所有变量,逐个以颜色名 对应色块的形式输出成表格。小结Textual 的主题系统是一条从11 个基础色自动派生整套 CSS 变量的生产线:明暗色阶、柔和色、彩色文本、控件专用变量都由ColorSystem.generate()统一生成,Theme.variables与App.get_theme_variable_defaults分别提供主题级与应用级的覆盖入口。实际开发中,建议按三步走:先用textual colors观察现有主题的变量全貌;再基于Theme构造器定义自己的主题并注册;最后利用$text-*与$-muted系列变量,让界面在任何主题下都保持可读。相关完整示例可继续阅读 docs/examples/themes/todo_app.py、docs/examples/themes/colored_text.py 与 docs/examples/themes/muted_backgrounds.py。【免费下载链接】textualThe lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.项目地址: https://gitcode.com/gh_mirrors/te/textual创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考