Modal
把主线之外的深度内容藏在一个点击展开的浮层里——保持正文紧凑,需要细节的读者按需获取。
When to use
- ✅ 可选深度("想看推导可以展开" 的数学证明)
- ✅ 大图 / 长代码(不想撑开页面)
- ✅ 历史 / 注释 / 致谢(不影响主线但应可访问)
- ✅ 风险声明 / 法律细则(必须存在但不主推)
- ✅ 媒体内容(视频 / 大图,节省首屏带宽)
- ❌ 必读内容(读者扫页必须看到的)→ 用 Callout
- ❌ 必填表单(modal 关掉就丢,不适合表单)→ 直接放 Interact
- ❌ 核心解释(隐藏起来就让人错过)→ 内联到正文
Basic syntax
\```modal trigger="查看技术细节"
这里是隐藏的深度内容——
- 详细的算法说明
- 边界条件
- 性能分析
\```
渲染:行内出现 "查看技术细节" 链接(带下划线 / 图标),点击 → 居中遮罩浮层显示 modal 内容;点 ✕ 或外部背景关闭。
All options
Block-level: ```modal
| Option | Type | Default | Purpose |
|---|---|---|---|
trigger |
string | (必填) | 触发链接的文字 |
title |
string | trigger 值 | modal header 显示的标题 |
width |
enum | medium |
modal 宽度(small / medium / large / full) |
closeOnEsc |
bool | true |
按 ESC 是否关闭 |
closeOnOutsideClick |
bool | true |
点 modal 外部是否关闭 |
trigger含空格请加引号:trigger="show details"。
Examples
Example 1:可选数学推导
**复利公式**:终值 = 本金 × (1 + 利率)^年限
\```modal trigger="数学推导(可选)"
**为什么是 (1+r)^n?**
第 1 年末本金:P × (1+r)
第 2 年末本金:P × (1+r) × (1+r) = P × (1+r)²
第 n 年末本金:P × (1+r)ⁿ
**为什么不是 P + n × r × P(单利)?**
单利忽略"利息再生利息"。复利的关键是 (1+r) 这个因子作用于每一年的总额,不只是初始本金。
\```
\```
*渲染:正文是简洁公式;想看推导的读者点 "数学推导(可选)" 浮层展开。新手不被推导淹没。*
### Example 2:大图
```markdown
系统架构概览(点击查看完整图):
\```modal trigger="🖼️ 完整架构图(5000×3000,含所有微服务)" width=full

详细说明:
- 蓝色:HTTP 服务
- 红色:异步队列
- 绿色:数据库
- 紫色:缓存层
\```
\```
*渲染:正文不显示大图,避免拖慢首屏;点击触发 modal 展开 full-width 大图 + 注释。*
### Example 3:长代码 / 完整脚本
```markdown
快速集成示例:
\```js
import { hydrateAll } from '@rho/md';
hydrateAll(container);
\```
\```modal trigger="📜 完整 setup 代码(含 unified pipeline 配置)"
\```js
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import { registerAllInnerPlugins, remarkPlugins, hydrateAll } from '@rho/md';
import '@rho/md/css';
registerAllInnerPlugins();
const processor = unified()
.use(remarkParse)
.use(remarkGfm);
remarkPlugins.forEach((p) => processor.use(p));
processor
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStringify, { allowDangerousHtml: true });
const html = String(await processor.process(markdownText));
container.innerHTML = html;
hydrateAll(container);
\```
\```
\```
*渲染:正文给最简集成;想看完整 setup 的开发者展开 modal 看 30 行配置。*
### Example 4:法律 / 风险声明
```markdown
注册即表示同意服务条款。
\```modal trigger="📄 完整服务条款 + 隐私政策" width=large
**服务条款(v3.2,2026-05-14)**
第 1 条 服务定义……
第 2 条 用户责任……
第 3 条 ……
**隐私政策(v2.1)**
我们收集……
我们不会……
\```
\```
*渲染:正文是简短同意提示;详细条款一键展开(满足 "必须存在 + 必须可访问",但不淹没注册流程)。*
---
## Plain-text fallback behavior
Modal 是 fenced code block ` ```modal `。在不支持的 reader 里:
→ 显示为**带 `modal` lang 的代码块**——读者看得到 trigger 文字 + 全部内容,**没有"隐藏"效果**。
具体各 reader:
| Reader | 渲染效果 |
|---|---|
| **Rho** | trigger 链接 + 点击弹出 modal 浮层 |
| **GitHub web** | 代码块(`modal` 当 lang label,trigger 字面量 + 全部内容明文) |
| **Obsidian** | 同上 |
| **VS Code 默认预览** | 同上 |
| **cat / less** | plain text |
> ⚠️ **Modal 的特殊性**:因为 fallback 是"展开成代码块",**modal 里的内容在不支持 reader 里依然全部可见**——这是好事(读者不会丢内容),但意味着:**modal 不能用来藏隐私 / 敏感信息**。它只是 UX 层面的 "整理",不是安全层面的 "隐藏"。
---
## Common pitfalls
### 1. trigger 文字过短 / 不清楚
```markdown
\```modal trigger="more"
真正想藏的复杂内容
\```
```
→ "more" 不告诉读者点了会出现什么。**trigger 应清晰描述 "点了会展开什么"**:
- ✅ "查看完整推导"
- ✅ "📜 完整 setup 代码"
- ✅ "📄 服务条款全文"
- ❌ "more" / "click here" / "..." / "details"
### 2. 把核心内容藏进 modal
```markdown
\```modal trigger="算法工作原理"
(完整算法解释,长 1000 字)
\```
```
→ 读者大概率不点,**就错过核心了**。
**判断标准**:如果 70%+ 读者都需要看,**不要 modal**——直接放正文。modal 是 "深度可选",不是 "必读但折叠"。
### 3. modal 套 modal
```markdown
\```modal trigger="外层"
\```modal trigger="内层" ← ❌ modal 套 modal,弹层叠加 UX 灾难
content
\```
\```
```
→ 用户点了外层 modal,里面又是个链接要点开第二层 modal——**读者迷失**。
**改进**:把内层 modal 的内容直接展开放在外层 modal 里。
### 4. modal 里嵌 interact
```markdown
\```modal trigger="试试这个滑块"
\```interact
slider x 0 10 5 1
template stl:
[选中] -> [{x}]
\```
\```
```
✅ 技术上 [Nested DSL](../advanced/nested-dsl.md) 支持。**但**:
- modal 关闭再打开时,slider state 通常会 reset
- 用户期待"演示"是直接看到的,不应藏进 modal
- **如果是核心 demo,别藏 modal,直接正文展开**
### 5. modal 里嵌表单 + 期望提交
```markdown
\```modal trigger="填这个表单"
\```interact
input email ""
input name ""
\```
\```
```
→ Rho 的 interact 状态**只在 reader 内存里**,不持久化、不发后端。**modal 关掉就丢全部输入**——用户白填。
**结论**:modal 不适合表单。表单直接放正文,且明确告诉用户 "纯演示,不会真提交"。
### 6. trigger 含未转义引号
```markdown
\```modal trigger="说"hello"会怎样" ← ❌ 嵌套引号,解析失败
```
→ 用全角 `"" ` 或避免嵌套:
```markdown
\```modal trigger="说 \"hello\" 会怎样" ← ✅ 转义
\```modal trigger="说"hello"会怎样" ← ✅ 全角
\```
```
### 7. 短内容用 modal
```markdown
\```modal trigger="点这"
就一句话。
\```
```
→ "点击 → 浮层 → 看到一句话 → 关掉" 的 UX 比直接读这句话还烦。**modal 只用于足以"撑开页面"的内容**(≥ 5 行 / 大图 / 长代码 / 表格)。
---
## Modal vs Callout vs Tabs — 选择依据
| 场景 | 用 |
|---|---|
| 主线之外的可选深度 | **Modal** |
| 必读的强调段 | [Callout](../static-blocks/callout.md) |
| 多视图选一(无须隐藏) | [Tabs](./tabs.md) |
| 教程式严格序列 | [Stepper](./stepper.md) |
---
## See also
- [Callout](../static-blocks/callout.md) — 必读强调(不隐藏)
- [Tabs](./tabs.md) — 多视图同空间(不隐藏)
- [Stepper](./stepper.md) — 严格序列
- [Nested DSL](../advanced/nested-dsl.md)
- [Plain-text fallback principle](../advanced/plain-text-fallback.md)