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
title string trigger value Title shown in modal header
width enum medium Modal width (small / medium / large / full)
closeOnEsc bool true Whether ESC closes
closeOnOutsideClick bool true Whether clicking outside the modal closes

Quote trigger if 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

```markdown
System architecture overview (click for full diagram):

\```modal trigger="🖼️ Full architecture diagram (5000×3000, all microservices)" width=full
![Full architecture](./images/full-architecture-5000x3000.png)

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

```markdown
Quick integration:

\```js
import { hydrateAll } from '@rho/md';
hydrateAll(container);
\```

\```modal trigger="📜 Full setup code (with unified pipeline config)"
\```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);
\```
\```
\```

*Renders: main text shows the minimal integration; developers wanting full setup expand the modal to see 30 lines of config.*

### Example 4: legal / risk disclosure

```markdown
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:


→ 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

```markdown
\```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

```markdown
\```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

```markdown
\```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

```markdown
\```modal trigger="try this slider"
\```interact
slider x 0 10 5 1
template stl:
[Selected] -> [{x}]
\```
\```
```

✅ Technically supported by [Nested DSL](../advanced/nested-dsl.md). **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

```markdown
\```modal trigger="fill this form"
\```interact
input email ""
input name ""
\```
\```
```

→ 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

```markdown
\```modal trigger="what if you say "hello""   ← ❌ Nested quotes break parsing
```

→ Use full-width `"" ` or avoid nesting:

```markdown
\```modal trigger="what if you say \"hello\""   ← ✅ Escaped
\```modal trigger="what if you say "hello""    ← ✅ Full-width
\```
```

### 7. Short content in a modal

```markdown
\```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](../static-blocks/callout.md) |
| Multiple views, pick one (no hiding needed) | [Tabs](./tabs.md) |
| Strict tutorial-like sequence | [Stepper](./stepper.md) |

---

## See also

- [Callout](../static-blocks/callout.md) — Must-read emphasis (no hiding)
- [Tabs](./tabs.md) — Multiple views, same space (no hiding)
- [Stepper](./stepper.md) — Strict sequence
- [Nested DSL](../advanced/nested-dsl.md)
- [Plain-text fallback principle](../advanced/plain-text-fallback.md)