Modal
Hide off-thread depth content behind a click-to-expand overlay — keep the main text compact while letting curious readers retrieve detail on demand.
When to use
- ✅ Optional depth ("expand to see derivation" math proofs)
- ✅ Large images / long code (don't bloat the page)
- ✅ History / footnotes / acknowledgments (don't affect the main thread but should be accessible)
- ✅ Risk disclosure / legal fine print (must exist but isn't featured)
- ✅ Media (videos / large images, save first-load bandwidth)
- ❌ Must-read content (anything readers must see while scanning) → use Callout
- ❌ Required forms (modal close discards state — bad for forms) → put Interact inline
- ❌ Core explanations (hiding makes readers miss it) → inline in main text
Basic syntax
```modal trigger="show technical details"
Hidden content here —
- detailed algorithm
- edge cases
- performance analysis
```
Renders: an inline "show technical details" link (underlined / with icon); click → centered overlay shows the modal content; click ✕ or background to close.
All options
Block-level: ```modal
| Option | Type | Default | Purpose |
|---|---|---|---|
trigger |
string | (required) | Text of the trigger link |
width |
enum | medium |
Modal width (small / medium / large / full) |
Quote
triggerif it has spaces:trigger="show details".
Examples
Example 1: optional math derivation
**Compound interest formula**: final value = principal × (1 + rate)^years
```modal trigger="Math derivation (optional)"
**Why (1+r)^n?**
End of year 1 principal: P × (1+r)
End of year 2 principal: P × (1+r) × (1+r) = P × (1+r)²
End of year n principal: P × (1+r)ⁿ
**Why not P + n × r × P (simple interest)?**
Simple interest ignores "interest earning interest." The key in compound interest is that the (1+r) factor applies to each year's running total, not just the original principal.
```
Renders: main text shows the clean formula; readers wanting derivation click "Math derivation (optional)" to expand. Beginners aren't drowned in proofs.
Example 2: large image
System architecture overview (click for full diagram):
```modal trigger="🖼️ Full architecture diagram (5000×3000, all microservices)" width=full
*(Full architecture diagram, 5000×3000, embedded here as a normal markdown image.)*
Legend:
- Blue: HTTP services
- Red: async queues
- Green: databases
- Purple: cache layer
```
Renders: large image isn't shown inline (avoids slowing first paint); click triggers the modal to expand the full-width diagram + legend.
Example 3: long code / full script
Quick start (one command):
```bash
python setup_project.py --init
```
````modal trigger="📜 Full setup script"
```py
#!/usr/bin/env python3
"""Full project setup script."""
import os
import sys
import json
def init_project(name):
os.makedirs(name, exist_ok=True)
config = {"name": name, "version": "0.1.0"}
with open(f"{name}/config.json", "w") as f:
json.dump(config, f, indent=2)
print(f"Project {name} initialized.")
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--init":
init_project("my-project")
```
````
Renders: main text shows the minimal one-line command; readers wanting the full script expand the modal to see the complete source.
Example 4: legal / risk disclosure
Signing up means you agree to our terms.
```modal trigger="📄 Full Terms of Service + Privacy Policy" width=large
**Terms of Service (v3.2, 2026-05-14)**
Section 1. Service definition...
Section 2. User responsibilities...
Section 3. ...
**Privacy Policy (v2.1)**
We collect...
We will not...
```
Renders: main text is a brief consent prompt; full terms expand on demand (satisfies "must exist + must be accessible" without overwhelming the signup flow).
Plain-text fallback behavior
Modal is a fenced code block ```modal. In readers without support:
```modal trigger="show technical details"
hidden content here
```
→ shows as a code block with modal lang — readers see the trigger text + all the content; no "hidden" effect.
Per-reader breakdown:
| Reader | Render |
|---|---|
| Rho | Trigger link + click pops up overlay |
| GitHub web | Code block (modal as lang label, trigger + all content as plain text) |
| Obsidian | Same |
| VS Code default preview | Same |
| cat / less | Plain text |
⚠️ Special note: because the fallback is "expand into a code block," modal content is fully visible in non-supporting readers. Good (readers don't lose content), but means: don't use modal to hide private / sensitive info. It's a UX-level "tidying" tool, not a security-level "hiding" tool.
Common pitfalls
1. trigger text too short / unclear
```modal trigger="more"
the actual hidden complex content
```
→ "more" doesn't tell readers what they'll see. trigger should clearly describe "what'll expand":
- ✅ "View full derivation"
- ✅ "📜 Full setup code"
- ✅ "📄 Full Terms of Service"
- ❌ "more" / "click here" / "..." / "details"
2. Hiding core content in a modal
```modal trigger="how the algorithm works"
(Full algorithm explanation, 1000 words)
```
→ Readers probably won't click — they'll miss the core. Rule: if 70%+ readers need to see it, don't modal it — put it inline. Modal is for "optional depth," not "must-read but folded."
3. Modal inside modal
```modal trigger="Outer"
```modal trigger="Inner" ← ❌ Modal in modal — overlay-on-overlay UX disaster
content
```
```
→ User clicks outer modal, finds another link to click for the second layer — readers get lost. Improvement: inline the inner modal's content directly in the outer modal.
4. interact inside modal
````modal trigger="try this slider"
```interact
slider x range=0..10 step=1 default=5
template stl:
[Selected] -> [{x}]
```
````
✅ Technically supported by Nested DSL. But:
- Closing and reopening the modal usually resets slider state
- Readers expect "demos" to be visible directly, not hidden in modals
- For a core demo, don't modal it — put it in the main text
5. Form in a modal expecting submission
````modal trigger="fill this form"
```interact
input email default=""
input name default=""
```
````
→ Rho's interact state is only in the reader's memory — not persisted, not sent to a backend. Closing the modal discards all input — user filled it for nothing. Conclusion: modals aren't for forms. Put forms inline + clearly state "demo only, no real submission."
6. trigger contains unescaped quotes
```modal trigger="what if you say "hello"" ← ❌ Nested quotes break parsing
→ Use full-width "" or avoid nesting:
```modal trigger="what if you say \"hello\"" ← ✅ Escaped
```modal trigger="what if you say "hello"" ← ✅ Full-width
```
7. Short content in a modal
```modal trigger="click here"
Just one sentence.
```
→ "Click → overlay → see one sentence → close" is more annoying than just reading the sentence. Use modal only for content big enough to "bloat the page" (≥ 5 lines / large image / long code / table).
Modal vs Callout vs Tabs — how to choose
| Scenario | Use |
|---|---|
| Off-thread optional depth | Modal |
| Must-read emphasized passage | Callout |
| Multiple views, pick one (no hiding needed) | Tabs |
| Strict tutorial-like sequence | Stepper |
See also
- Callout — Must-read emphasis (no hiding)
- Tabs — Multiple views, same space (no hiding)
- Stepper — Strict sequence
- Nested DSL
- Plain-text fallback principle