We use : to express hierarchy inside URL fragments. Example: https://example.com/page#section:subsection uses : to separate a primary section (section) from a nested part (subsection). The full value appears in an element’s id, e.g. section:subsection.
We need a delimiter that clearly separates logical segments without clashing with ordinary heading slugs or requiring extra query parameters. : fits that role:
- or _, which already saturate slugs.-; literal colons almost never appear organically.: in the reserved gen-delims set (Section 2.2). The fragment syntax (Section 3.5) does not define internal structure, and Appendix A (pchar) allows :. That leaves the colon semantically free for client-side interpretation. If a literal colon must appear within a segment, encode it as %3A so parsing treats it as data, not a separator (e.g. #token:pattern%3Aliteral:details → token, pattern:literal, details).Typical patterns (1-3 levels):
#<primary>
#<primary>:<secondary>
#<primary>:<secondary>:<tertiary>
Guidelines:
lowercase-with-dashes).id exactly matches the full fragment.| Use case | Fragment | Meaning |
|---|---|---|
| Section + tab | api:props | Props tab inside API section |
| Demo + variant | button-demo:styled | Styled variant of a demo |
| Composite (section → grouping → item) | theming:palette:primary | Primary palette subsection |
| Code view mode | code-sample:ts | Show TypeScript version |
Do:
: only for stable parent→child (or mode) hierarchy.section: → section).Don't:
: with arbitrary flags that aren't structural.section/sub:part).api:props must map to an actual element for consistent scrolling and focus. Secondary (and tertiary) segments then drive local state (which tab / variant) after the primary region is in view.#api) keep working. Multi-segment forms layer precision without breaking earlier links.Goal: The Back button should move between major sections, not every small variant change.
If a feature must record variant transitions individually, document the exception and its justification.
Modern browsers support the Text Fragment syntax: #:~:text=... which allows deep linking to arbitrary text ranges. We MUST avoid colliding with this mechanism.
Rules:
:~:text= as reserved for the browser. Our hierarchical parsing stops before that sequence.:~:.#api:props:~:text=Primary%20Color — our hierarchy is api:props; the remainder is the text fragment directive.:~: and let the UA handle highlighting the text range.Parser note (conceptual): Split off the reserved suffix first, then process hierarchy on the remaining prefix.
Colon-separated IDs are neutral for assistive tech. Ensure:
aria-* attributes.id SafetyStandards summary:
id must be unique and free of whitespace; : is allowed.:.:; no internal semantics are imposed.Practical rules:
Uniqueness: Every full multi-segment value (e.g. theming:palette:primary) must be unique.
Stability: Changing a primary segment breaks inbound links; avoid unless necessary.
Whitespace: Reject IDs with spaces/newlines before adding hierarchy.
CSS: Escape colons in raw CSS selectors:
/* Incorrect (interpreted as pseudo-class) */
/* #api:props { ... } */
/* Correct */
#api\:props {
}
#theming\:palette\:primary {
}
JavaScript helper:
function idToCssSelector(id) {
return `#${id.replace(/:/g, '\\:')}`;
}
Testing: Prefer data-* attributes over complex escaped selectors.
Slugification: Remove or replace colons from raw heading text before appending structural segments.
One-to-one mapping: Each emitted multi-segment fragment corresponds to a concrete element (heading, panel container, etc.).
Edge cases:
section::variant) → invalid; fail fast.section:) → normalize to section.%3A) remains data; do not decode before splitting.Migration strategy (if delimiter ever changes):
:~:text=, separate the prefix (hierarchy) from the text fragment suffix; ignore the suffix for hierarchy parsing.component:lines-10-20) would need a clearly documented pattern.Why not -? Already pervasive in slugs; parsing hierarchy would be unreliable.
Why not /? Interferes with routing assumptions and is less copy/paste friendly in some contexts.
Do we percent-encode :? Not for structural separators. Only encode when a literal colon is part of the content within a single segment.
Multiple flags like :dark:compact? Combine into a single variant token instead of stacking arbitrary segments.
If you add a new semantic layer (new segment meaning), update this document and reference the change (issue / PR).