Kitchen sink
A page that exercises everything the web part renders, so a theme can be judged at a glance. Switch theme and colour mode with the controls above.
Text
Regular paragraph text with bold, italic, strikethrough, inline code,
a link to SharePoint, nothing exotic,
and a keyboard shortcut like Ctrl + K.
One tilde strikes through as well as two, so this and this match.
With Tags on, #kitchen-sink and #themes/obsidian are shown as tags. Superscript is written 106, and a subscript comes from maths, , or from markup. Emoji render from their shortcodes: 🚀 ⚠️ ✅. An abbreviation carries its meaning on hover, so HTML and CSS explain themselves the first time someone meets them.
A plain blockquote, for quoting someone rather than flagging something. It should read as quiet, not as an alert.
Callouts
Useful information that users should know, even when skimming content.
Helpful advice for doing things better or more easily.
Key information users need to know to achieve their goal.
Urgent info that needs immediate user attention to avoid problems.
Advises about risks or negative outcomes of certain actions.
Yes: abstract, todo, success, question, failure, danger, bug, example and quote.
Nested content works: lists, code, even other callouts.
npm run packageFoldable, collapsed
Click the title to open. This is a <details> element, so it prints expanded
and works without JavaScript.
Legacy Wiki.js syntax from the older web part still renders.
Links between pages
With Wiki links on, [[Another page]] links to that file in the same
folder, the way Obsidian and older wikis write one. A link to deploy points
at a page that is there; a link to a page nobody wrote is marked when there
is a library behind the page to ask, because the folder is listed once and every
link into it answered from that. You can also write
a label of your own or jump to deploy › Rollback, and Images
goes to a heading in this document. A link inside a table is written with an
escaped pipe, the runbook, so the pipe does not end the cell.
This paragraph is named, so a link elsewhere can point straight at it.
Images
A relative source resolves against the folder the markdown lives in, the way it does on GitHub, rather than against the page it is rendered on:
An embed puts a picture on the page the way Obsidian writes one,
, and an embed of something that cannot be put on a page
without fetching it, deploy, is marked as an embed and linked instead.
An absolute source is left exactly as written, so a data URI renders without
fetching anything. Note that markdown-it accepts data: only for png, gif,
jpeg and webp; an SVG data URI is refused, because an SVG can carry script: inline,
in the middle of a sentence, sitting on the text baseline.
Reference style works too, which keeps a long source out of the prose:
A width can be asked for after a pipe, the way Obsidian writes it, as
![alt|240] or ![alt|240x120]. The number is set as the image's width rather
than as a style, so the browser knows the shape of the picture before it has
loaded one and the text below does not jump as it arrives. Height is left to
the stylesheet, so the aspect ratio holds:
A document can place one picture itself, whatever the page is set to, by giving the image a class. This one asks to be centred:
An image that is a paragraph on its own and carries a title becomes a figure, with the title as its caption rather than as a tooltip nobody on a touch screen can see:
Code
/** Resolves the configured mode into a concrete light or dark value. */export function resolveMode(mode: ColorMode, isInverted?: boolean): ResolvedMode { if (mode === 'light' || mode === 'dark') { return mode; } if (typeof isInverted === 'boolean') { return isInverted ? 'dark' : 'light'; } return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';}from dataclasses import dataclass@dataclassclass Theme: name: str dark: bool = False def label(self) -> str: """Human readable name.""" return f"{self.name} ({'dark' if self.dark else 'light'})"print(Theme("Obsidian", dark=True).label()) # Obsidian (dark)A fence can override the page's own settings. This one wraps its long lines and turns the gutter off, whatever the web part is set to:
# Deploy the package to the tenant app catalog$ctx = Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/apps" -InteractiveAdd-PnPApp -Path .\sharepoint\solution\markstrata.sppkg -Scope Tenant -PublishGet-PnPApp | Where-Object { $_.Title -like "*markdown*" } | Format-Table Title, DeployedAnd this one forces the gutter on and scrolls instead of wrapping:
{ "themeFamily": "obsidian", "colorMode": "dark", "showLineNumbers": true, "features": ["mermaid", "katex", "callouts"], "enabled": true}A fence can also call out the lines that matter, the way Docusaurus and VitePress write it. The rest of the block is faded rather than the called lines being tinted, so it reads the same against every theme's syntax colours, and hovering the block brings it all back:
export function resolveMode(mode: ColorMode, isInverted?: boolean): ResolvedMode { if (mode === 'light' || mode === 'dark') { return mode; } if (typeof isInverted === 'boolean') { return isInverted ? 'dark' : 'light'; } return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';}A fence can cap its own height and scroll inside instead of running down the
page. short is about ten lines, medium about twenty-five, and full, the
default, is as tall as the code:
from dataclasses import dataclass, fieldfrom typing import Dict, List@dataclassclass Settings: theme: str = "obsidian" mode: str = "dark" features: List[str] = field(default_factory=list) limits: Dict[str, int] = field(default_factory=dict) def enabled(self, name: str) -> bool: return name in self.features def limit(self, name: str, fallback: int = 0) -> int: return self.limits.get(name, fallback) def describe(self) -> str: on = ", ".join(sorted(self.features)) or "nothing" return f"{self.theme}/{self.mode} with {on}"settings = Settings(features=["mermaid", "katex"], limits={"toc": 3})print(settings.describe())print(settings.limit("toc"), settings.limit("width", 860))A fence can also point at a file somewhere else instead of carrying a body, and quote a few lines out of it by the fragment GitHub puts in the address bar. The block is drawn waiting and filled in once that server answers, so nothing in the document is held up by it:
- --strata-code-bg: #212121;- text-shadow: 0 -0.1em 0.2em #000;+ --strata-code-bg: var(--strata-code-bg);+ /* contrast comes from the theme, not from a shadow */An indented code block, with no language:
$env:SPFX_SERVE_TENANT_DOMAIN = "contoso"gulp serve --nobrowserTables
| Theme | Light background | Dark background | Code block | Callout shape |
|---|---|---|---|---|
| GitHub | #ffffff |
#0d1117 |
flat fill, 6px radius | accent bar, no fill |
| Obsidian | #ffffff |
#1e1e1e |
fill, 8px radius | tinted panel + title row |
| VS Code | #ffffff |
#1f1f1f |
bordered, 3px radius | tint + 3px bar |
| Aligned left | Centered | Right |
|---|---|---|
| one | two | 3 |
| four | five | 60 |
Cells can span. ^^ merges a cell with the one above it, and a doubled ||
lets a cell run across the column to its right:
| Service | Database | Host |
|---|---|---|
| Orders | orders-db | db01.example.com |
| Catalogue | catalog-db | db02.example.com |
| search-db | db02.example.com | |
| Both catalogue databases share a host | db02.example.com | |
Lists
- Ordered item
- Second item
- Nested ordered
- Another
- Third
-
Unordered item
-
With a nested list
- Second level
- Third level
- Second level
-
Fix code block contrast
-
Add GitHub alerts and Obsidian callouts
-
Ship it
- Term
- A definition list entry.
- Another term
- Its definition.
Math
Inline: the Lorentz factor is , and GitHub's backtick form beside it.
The sum of the first n numbers is:
A fence labelled math is the same block:
Diagram
flowchart TD
A[Markdown source] --> B{Theme}
B -->|GitHub| C[Primer palette]
B -->|Obsidian| D[Obsidian palette]
B -->|VS Code| E[Dark+/Light+ palette]
C --> F[Rendered page]
D --> F
E --> F
A gantt chart, which mermaid lays out differently from a flowchart and which is the one that used to render its labels too small to read:
gantt
title A release week
dateFormat YYYY-MM-DD
axisFormat %d %b
todayMarker off
section Build
Cut the branch :done, 2026-01-05, 1d
Review :active, 2026-01-06, 2d
section Ship
Tag the release :milestone, crit, 2026-01-08, 0d
Watch the deploy :2026-01-08, 2d
Footnotes
Themes are defined entirely in CSS custom properties[1], so adding a fourth one is a data change[2].