Components
The complete component vocabulary, rendered on the page that documents it, including callouts, cards, steps, tabs, and trees.
Prefer a semantic PascalCase tag over styling content with className. The
complete vocabulary is declared in
src/components/mdx-components.ts,
with one line per author-facing name. An unknown tag fails the build and names
itself in the error.
Everything on this page is live. Each example shows exactly what the component does.
The vocabulary
| Group | Tags |
|---|---|
| Structure | Tabs, Tab, CodeGroup, CodeBlock, Steps, Step, Columns, Column, Panel, Frame |
| Emphasis | Callout, Note, Warning, Info, Tip, Check, Danger, Banner, Badge, Update, Tooltip |
| Content and API | Prompt, ParamField, ResponseField, RequestExample, ResponseExample, Examples, Endpoint |
| Disclosure | Accordion, AccordionGroup, Expandable |
| Audience and variants | View, Visibility |
| Navigation and visuals | Card, CardGroup, Tile, Tree, TreeFolder, TreeFile, Icon, Mermaid, Color, ColorRow, ColorItem |
Some tags have shorter aliases. Duvlify keeps them because they match the
notation most documentation already uses: FileTree/Folder/File for the
tree, Response for ResponseField, and Color.Row/Color.Item and
Tree.Folder/Tree.File as member expressions of their parent. Each alias is
the same component as its full name.
Callouts
A callout is one bordered, tinted aside. The tone prop selects both the tint
and the icon. Note, Tip, Info, Check, Warning, and Danger are
one-line wrappers that each pick a tone, so a page never has to name a colour
directly.
<Callout title="A callout with a title" tone="info">
Reach for a callout when the statement is true of one passage.
</Callout>
<Tip>A tip, written as `<Tip>` rather than `<Callout tone="tip">`.</Tip>Available tones: info, note, success, tip, check, warning, danger.
Cards and tiles
A linked card
With href set, the card renders as an anchor and grows an arrow.
An unlinked card
Without href, the card is a plain titled block.
<CardGroup cols={2}>
<Card title="A linked card" href="/getting-started">
<Icon slot="icon" name="rocket" size={22} />
Given `href` it renders as an anchor and grows an arrow.
</Card>
</CardGroup>Two meanings of cols
Both <Columns> and <CardGroup> take cols, but the prop means different
things for each. Knowing this helps explain why a group can show three cards
when four were asked for.
cols means | cols={1} | cols={4} in the reading column | |
|---|---|---|---|
Columns | exactly this many | one full-width cell | four narrow cells |
CardGroup | at most this many | one full-width card | three, then four in a wider layout |
CardGroup packs as many ~210px cards as fit and stops at cols. This keeps
each card at a readable width, and no page needs its own breakpoint. Columns
follows the exact layout the author wrote. Both props take values 1 to 4 and
collapse to one column on a phone.
Steps
Numbered automatically
The marker counts steps for you. Pass icon to replace the number. Pass
stepNumber to continue a sequence that started elsewhere.
Any content inside
Including code fences, callouts and tables.
<Steps>
<Step title="Numbered automatically">The marker counts for you.</Step>
<Step title="Any content inside">Including code fences and callouts.</Step>
</Steps>Tabs
Tab groups synchronize when they offer the same labels in the same order. For example, every “npm / pnpm / yarn” group on a site moves together, and the site remembers the choice for the next page. Groups that share only one label do not synchronize. A group offering “REST” alongside two other options is a different group from one offering “REST” alongside four options. Treating them as the same group made unrelated sections jump together.
| Prop | Effect |
|---|---|
sync={false} | Keeps this group local, on Tabs, CodeGroup or RequestExample |
defaultTabIndex={1} | Starts on the second panel. A reader’s remembered choice takes priority over it |
dropdown | On CodeGroup, for when a horizontal language strip would be crowded |
Disclosure
An accordionWith a description line
Closed by default. Pass defaultOpen to start it open. Pass icon for a
glyph beside the label.
A second one
Grouped in an <AccordionGroup>, so the accordions share one border.
<Expandable> is the lighter variant. Use it to reveal a nested object’s
fields inside a parameter list, rather than a section of prose.
Parameter fields
page_idstringrequiredThe page identifier. path, query, header, and body each set where
the parameter lives, and the label reflects the choice.
limitintegerdefault: 20Records per page. The API clamps requests above the maximum instead of rejecting them.
<ParamField path="page_id" type="string" required>
The page identifier.
</ParamField>On an OpenAPI endpoint page you do not write these by hand. The framework generates them from the spec. Write them yourself only for an API this framework does not drive, or for a configuration object.
Badges, updates and tooltips
Default green outlined with an iconA tooltip wraps inline text.
What changed
<Update> is the changelog entry: a dated block with optional tags, meant
to stack down a release-notes page. The label becomes the anchor, in
slugified form. This gives a date written for a reader a usable permalink
too.
Banners
<Banner> states something true of one page, in the reading column:
For an announcement on every page, set site.banner in
src/docs.config.ts instead. This creates the strip fixed above the navbar,
which this site is using right now. There is deliberately no per-page
equivalent of that strip. A fixed bar states something true of the whole
documentation set. Something true of one page belongs in the page instead.
The site remembers a dismissal against the exact text of the banner. Publishing new wording shows the banner again to everyone who dismissed the previous version. This same behaviour makes the strip unsuitable for anything a reader must not be able to hide.
File trees
app
guides
<Tree>
<TreeFolder name="app">
<TreeFile name="index.mdx" />
</TreeFolder>
</Tree>FileTree, Folder, and File are aliases for the same three components.
Tree.Folder and Tree.File are the same components again, written as member
expressions of their parent. The tree above uses this member-expression form.
All three notations leave identical Markdown in the page’s twin, so an agent
reading it sees a folder listing rather than component syntax.
Colours
<Color.Item> takes either one value or a { light, dark } pair. It captions
the swatch with exactly what you wrote, not a sanitised version of it. Use
variant="table" for a labelled palette. Leave it off for a compact row.
Columns
Columns follows the layout you drew.The right rail
Top-level Panel, RequestExample, and ResponseExample content occupies the
sticky right rail on desktop, and returns to the article on smaller screens.
Set rightRail: custom in frontmatter only on a page that deliberately
replaces the table of contents with those components. Such a page uses the
same layout as an OpenAPI endpoint page: the same wider three-track frame, and
the same sticky panel. Both pages share one shape: prose in the middle,
examples alongside. Below 1280px, the build hides the rail column either way.
Page-wide variants
Use <View> for one page-wide language or framework selector. Use <Tabs>
for alternatives local to one passage. Two or more views make the selector
appear. One view on its own renders as an ordinary section. This is what
makes it safe to write a page one view at a time.
With two or more views on a page, a single selector appears above them and switches the whole page at once. This is the TypeScript variant.
This is the Python variant. Only one view is visible at a time. The
selector is the platform’s own <select> element, so an option cannot
carry an icon. This is why a View is labelled by its title alone.
For content that differs between readers and models, rather than between
frameworks, use <Visibility>:
You are reading the rendered page, so this paragraph appears. The Markdown twin of this page carries a different paragraph in its place.
Full explanation in Authoring.
Diagrams
A ```mermaid fence is the usual way to add a diagram. See
Code and diagrams. Use the <Mermaid>
component for one-liners and for the interactive controls:
actions={true} adds pan, zoom, reset, and a fullscreen viewer on hover or
keyboard focus. placement positions the inline controls. Set
actions={false} to make a deliberately static diagram.
Icons
Every icon on the site comes from Lucide through
<Icon name="…" />, never a raw emoji or Unicode glyph. A plain character
renders without error, but it looks inconsistent next to a Lucide icon: the
weight, baseline, and style differ.
name is a short alias, not the Lucide name directly. See the icons map in
src/components/Icon.astro
for what is available. If the icon you need is not there, import it from
@lucide/astro and add one line to that map.