| name | slidev-theme-ustc |
| description | Use when creating, editing, converting, exporting, or designing academic Slidev decks with slidev-theme-ustc, including group-meeting reports, literature surveys, conference talks, PPT-to-Slidev conversion, figure/PDF asset ingestion, layout density fixes, and ugly/empty/cramped slide repair. |
slidev-theme-ustc
A clean academic Slidev theme for USTC-style presentations.
First classify the deck
Before drafting or editing, classify the task and read the matching reference:
If the user gives a PPT, distinguish materials-only PPT from drafted deck PPT.
Materials-only PPTs can be reorganized. Drafted PPTs with titles/order/notes should
be treated as structural references unless the user asks for a redesign.
Group meeting and conference defaults differ: group meeting decks normally omit
affiliations; formal conference talks normally include authors, affiliations, venue,
date, and presenter. Do not let one scenario's cover metadata leak into the other.
Designing a deck, not just operating the template? Read
references/authoring-academic-decks.md first.
It covers how to make slides that are logically clear, rigorous, and not ugly:
the argument spine, the layout decision tree, the density rubric, figure handling,
academic taboos, citation discipline, and the render โ measure โ fix audit loop
(scripts/audit-layout.mjs). This file below is the API reference โ what knobs
exist; that file is which to turn and why.
Designing a deck โ 10 rules (start here)
The load-bearing rules, condensed. The why/how (layout decision tree, density rubric,
taboos, citations) is in references/authoring-academic-decks.md.
- A deck is an argument, not a document โ pick one claim; rest โ
backup.
- One job per slide โ one clear point each; split if it does two things.
- Structure, not prose โ short bullet fragments, never paragraphs (walls of text are the #1 tell of a bad deck).
- Motivation before formalism โ "Why?" before "What?"; task + motivation before method/results.
- Dense content, not dense mode โ cover a lot via structure and terse wording, not by reaching for
density: dense to shrink text. dense + a half-empty slide is the worst combination.
- Fill the canvas (~85โ100%) by adding substance, never by cramming โ this style dislikes empty corners, but reach the target by adding a figure / merging thin slides / rebalancing; legibility always wins over a fill number.
- Wide figures (aspect ratio > ~2.5) span full width โ never in a
split column (they go small and leave a void below).
- Cite on the slide, verify every citation โ per-page footnotes, no references slide; credit borrowed figures; never invent a citation.
- Academic voice โ no colloquialisms, filler, "not X but Y" templates, or invented abbreviations; don't oversell (no "SOTA" for older work). See references/style/wording.md.
- Measure, then commit โ run
scripts/audit-layout.mjs + eyeball a --shot screenshot before declaring a slide done; when you cut content to fit, relocate it to backup โ never silently delete.
Density tiers: normal (default) โ compact (middle) โ dense (tightest); reach for the largest that fits. The visual target is clean, compact, and readable: neither sparse nor cramped. See references/style/visual-rubric.md. The end slide MUST have an # h1 (the large centered closing line).
Setup
theme: ./
theme: @luocfprime/slidev-theme-ustc
Global Frontmatter
Set in the first slide of the deck:
---
theme: ./
talkTitle: 'Your Presentation Title'
conference: 'CVPR 2025'
date: 'June 2025'
presenterName: 'Alice Chen'
authors:
- name: 'Alice Chen'
affiliations: ['University of Science and Technology of China']
- name: 'Bob Smith'
affiliations: ['MIT', 'USTC']
marks: ['โ ']
- name: 'Carol Lee'
affiliations: ['Peking University']
authorMarks:
'โ ': 'Equal contribution'
sectionBar: true
sectionBarMode: full
lineHeight: 1.6
flowGap: normal
figurePrefix: Figure
tablePrefix: Table
figureNumberSuffix: ': '
tableNumberSuffix: ': '
figureZoom: false
---
Authors format: array of { name, affiliations, marks? } objects. presenterName sets who is underlined (defaults to first author). Institutions get sequential superscript numbers in order of first appearance. marks are per-author symbols (e.g. โ , *) displayed as superscripts after the institute number; authorMarks maps each symbol to its legend text (rendered below the affiliations line).
Global body rhythm: lineHeight and flowGap may be set in the first
slide's global frontmatter as deck-wide defaults for default / content /
split body slides. A body slide can override either value locally.
lineHeight is a unitless CSS line-height multiplier. flowGap controls
vertical spacing between adjacent top-level content blocks (markdown tables,
theme components, media blocks, Plotly, code fences, Mermaid diagrams, Typst
output, grids, and split-column top-level blocks); it does not affect internal
spacing such as image-caption gaps, component title/body gaps, list item gaps,
or split/Grid column gaps. Accepted flowGap presets: none (0), tight
(0.45rem), normal (0.75rem), loose (1rem), or a CSS length string
such as 0.6rem or 8px.
Layouts
cover
Title slide with author/affiliation display.
---
layout: cover
talkTitle: '...'
subtitle: '...'
presenterName: 'Alice Chen'
authors: [...]
authorMarks:
'โ ': 'Equal contribution'
'*': 'Corresponding author'
conference: '...'
date: '...'
showLogo: true
logoSrc: /ustc/logo.svg
logoAlt: USTC logo
background: '#1a2a4a'
---
default / content
Standard body slide. default is the fallback when no layout: is specified; content is an identical alias commonly used to signal a body slide explicitly. Both accept the same props.
---
layout: default
density: normal
margin: normal
lineHeight: 1.8
flowGap: normal
align: left
footnote: overlay
footer: true
footerMode: full
sectionBar: true
sectionBarMode: full
subtitle: '...'
background: '#f5f5f5'
---
split
Two-column layout.
---
layout: split
ratio: "2:1"
gap: md
subtitle: "..."
---
::left::
Left column content
::right::
Right column content
toc
Auto-generated table of contents from section slides.
---
layout: toc
variant: arrow
highlight: 2
columns: 1
footer: true
footerMode: full
background: '#f5f5f5'
---
The # Title line is optional. If omitted, the TOC has no heading. Write # Contents (or any text) when the page needs an explicit title.
Sections use h1 text from each section slide (or sectionLabel if set). The arrow variant displays 01, 02, โฆ; classic displays ยง1, ยง2, โฆ.
TOC variant rubric:
variant: arrow (default): use for 3โ5 sections, and up to about 6 if labels are short. Best for opening agenda pages and section-transition pages; usually omit the H1 and avoid numbered section labels such as ไธใ. Use sectionLabel on section slides when the section slide title itself needs numbering. columns has no effect.
variant: classic, columns: 1: use for 6โ8 sections when a linear reading order matters.
variant: classic, columns: 2: use for 9โ14 sections when space matters more than a strong visual agenda.
- 15+ sections: avoid listing every section automatically; merge sections, split into grouped TOCs, or hand-author a shorter structure page.
Compatibility: existing decks that set columns without variant keep the old classic appearance. New decks should set variant: classic explicitly when they want columns.
section
Section divider slide.
---
layout: section
sectionLabel: 'Short Label'
sectionBarMode: minimal
footer: true
footerMode: full
background: '/bg-section.jpg'
---
end
Closing/thank-you slide (centered content).
---
layout: end
showLogo: false
footer: true
footerMode: full
background: '#1a2a4a'
---
::contact::
name@ustc.edu.cn
The end slide MUST have an # h1 โ that h1 is the large centered line (e.g.
# Thank you, or the closing line in the deck's language). A common mistake is
shipping an end slide with only body
text and no h1, which renders small and off-center. Always include the h1.
The end slide is a courtesy close; it does not replace a substantive
conclusions / takeaways slide, which must come before it. The ::contact:: slot
renders below the main content.
backup
Appendix marker. Everything after this slide gets A.N page numbering in the footer.
---
layout: backup
footer: true
background: '#f5f5f5'
---
blank
Full-bleed canvas with no padding or chrome. Use with <Abs> for precise positioning.
---
layout: blank
background: '/bg-fullbleed.jpg'
---
Background images on any layout
Every layout (cover, default / content, split, section, toc, end, backup, blank) accepts the same background: frontmatter โ either a CSS color (#1a2a4a, rgb(...), named colors) or an image path (/bg.jpg, https://...). Images render at background-size: cover; background-position: center.
Readability โ overlay behavior differs by layout:
cover automatically adds a left-to-right white gradient (rgba(255,255,255,1) โ rgba(255,255,255,0.85)) on top of the image so the left-aligned title/authors stay readable.
- All other layouts render the image raw, with no overlay. If body text starts becoming hard to read on a busy image, add an overlay manually in the slide's
<style> block:
<style>
.slidev-layout {
background-image:
linear-gradient(rgba(255, 255, 255, 0.88), rgba(255, 255, 255, 0.88)),
url('/bg.jpg');
background-size: cover;
background-position: center;
}
</style>
This overrides the background-image produced by the frontmatter, so you can drop the background: prop on the slide if you're styling it manually. The same trick works for tinted color overlays (linear-gradient(rgba(30,76,144,0.4), rgba(30,76,144,0.4))) when the image is light and you want a brand-tinted wash.
Components
Grid, Abs, VSpace โ layout helpers. NumberedList, Note, Block, Box โ structural content blocks. Callout, FigureBlock, TableBlock, ResultBox, Takeaway โ content blocks. Badge โ inline pill tag. VideoBlock, PlotlyGraph, QRCode โ media.
All are auto-imported by Slidev. For full prop tables see references/api/components.md.
When to use which content component
| Component | Semantic role | Typical use |
|---|
<Block> | Generic named container | Definitions, lemmas, frameworks โ any "labeled box" without emotional valence |
<NumberedList> | Ordered module list | Parts, stages, or ranked components where numbering matters but a process diagram would overstate the relation |
<Note> | Lightweight title-body pair | Q&A, summary/specification, assumption/limitation, observation/implication |
<Callout> | Typed supplementary note | Tips, warnings, important caveats โ when the type of side information matters |
<Takeaway> | The single most important point | One bold conclusion per slide; no props, forces brevity |
<ResultBox> | Quantitative or formal result | Experimental numbers, theorem statements, final answers |
<Badge> | Inline metadata tag (not a box) | Venue / year / status / CCF rank / dataset / links โ flowing inline with text |
Choose components by semantic role before visual taste:
- If items are peers, start with the same component treatment.
- If two treatments differ, be able to name the semantic difference: different information type, different reading role, or different emphasis intent.
- If the only reason for changing component type is "visual variety", use the same component and fix rhythm with
Grid, flowGap, or VSpace instead.
- Use layout first, emphasis second: a better column ratio, alignment, or spacer is usually cleaner than upgrading a paragraph into a louder box.
Rules of thumb: <Callout type="warning"> is for the audience, not the presenter. <Callout> has visible type color and a left rail; use it for typed side notes, not for the slide's main claim. <Block> is neutral โ reach for it when a named container is useful but no warning/tip/result semantics are intended. <Box>, <Block>, and quiet <Callout :showIcon="false"> are all middle-weight containers; pick among them by meaning, not by trying to make every region look different.
Visual emphasis budget
Every component spends visual attention. A border, background fill, icon,
strong color, or oversized type each adds emphasis. Match the visual weight to
the information's role: high-emphasis components should be rare; low-emphasis
components can appear more often when they form a clean structure.
These are recommended maximums per component type, not hard validation rules.
They are also not shared quotas by emphasis tier. The real failure mode is
mixing many different high-visibility treatments on one slide so that nothing
has priority.
| Component | Visual emphasis | Recommended max per slide |
|---|
<Takeaway> | High | 1 |
<Callout> | Medium; medium-high when filled/icon-heavy | 1; 2 only for brief side notes with distinct types |
<ResultBox> | Medium-high | 2 |
<NumberedList> | Medium | 1 group; 2 groups only for a clear split-column process |
<Block> | Medium | 2; in split, 2-3 when distributed across both columns |
<Box> | Low-medium | 2; in split, 2-3 when distributed across both columns |
<Badge> | Low, but easily fragmented | 6; more only as a deliberate legend or tag row |
<Note> | Low | 4; in split, 5-6 when used as lightweight title-body pairs |
<Note> is a low-emphasis title-body pair for Q&A,
summary/specification, assumption/limitation, or observation/implication
content: more structured than a plain list, but lighter than <Block>,
<Callout>, or <ResultBox>. Stack multiple <Note>s naturally; do not wrap
them in a separate <Notes> container.
<Callout> uses a GitHub-like rail style by default: colored left rail, title
icon on, and no filled background. Set :showIcon="false" for a quieter rail
or filled for the older, more salient pale background. Treat it as typed
supplementary information, not as a high-level conclusion box. It is easy to
overuse because the rail/color implies semantic urgency even when the content is
only a normal note. Use <Takeaway> or <ResultBox> when the message should
carry the slide.
Repeating one low- or medium-emphasis component in a strict grid, matrix, or
comparison is usually calmer than mixing many component styles. Mixed
components are correct only when the slide has mixed semantic roles (for
example: main comparison + warning caveat + final quantitative result). A page
with several peer <Block>s in a 2x2 matrix can work; a page with a
<Takeaway>, a <Callout>, two <ResultBox>es, and multiple boxes usually
over-spends the attention budget. Media/content carriers such as <FigureBlock>,
<TableBlock>, <VideoBlock>, <PlotlyGraph>, and <QRCode> are governed by
content/layout needs and are intentionally not included in this structural
emphasis table.
Quick reference
<Grid cols="2" gap="md" alignY="top">...</Grid>
<VSpace size="1rem" />
<VSpace size="-0.5rem" />
<!-- explicit one-off vertical spacing; prefer over <br>; negative values are an escape hatch -->
<NumberedList
:items="[
{ title: 'Collect data', body: 'gather sources and normalize fields' },
{ title: 'Run analysis', body: 'apply the shared scoring protocol' },
{ title: 'Write summary', body: 'report findings and remaining caveats' },
]"
/>
<Note title="Input">
...
</Note>
<Note title="Output" color="#92400e" :divider="false">
...
</Note>
<Block title="Definition">
...
</Block>
<Block title="Recovered state" color="#065f46">
...
</Block>
<Box bg="blue-pale" borderColor="var(--ustc-blue)" radius="4px">
...
</Box>
<Callout type="tip" title="Insight">
...
</Callout>
<!-- types: note tip warning important example -->
<Takeaway>
Key point.
</Takeaway>
<ResultBox title="Result" bg="blue-pale" borderColor="var(--ustc-blue)">
...
</ResultBox>
<Badge variant="solid" color="#c0392b">CCF A</Badge> <Badge variant="soft"><mdi-github /> Code</Badge>
<!-- variants: soft solid outline; any color; href turns it into a link -->
<Badge href="https://arxiv.org/abs/2509.20358" color="#b31b1b"><mdi-school /> arXiv</Badge>
<FigureBlock src="/img.png" caption="Caption" width="80%" />
<FigureBlock src="/img.png" caption="Scheme" prefix="Scheme" />
<!-- shows "Scheme 2" if global counter is at 2 โ prefix changes the label text, the global number stays -->
<FigureBlock wip src="https://placehold.co/800x600" caption="Not ready yet" />
<!-- red WIP badge; use placehold.co to hold the correct aspect ratio -->
<!-- #caption slot for footnote refs / links / Vue components โ REQUIRES blank lines around <template> and its content for Slidev to process inside as markdown -->
<FigureBlock src="/img.png" width="60%">
<template #caption>
Caption with [^1] footnote ref
</template>
</FigureBlock>
<TableBlock caption="Table title">| col | ... |</TableBlock>
<TableBlock wip caption="Table title">| col | ... |</TableBlock>
<!-- red WIP badge inline after caption -->
<!-- TableBlock #caption slot โ same blank-line requirement; default slot still holds the table markdown -->
<TableBlock width="70%">
<template #caption>
Cite[^1] in caption
</template>
| col | ... |
</TableBlock>
<Abs x="200" y="100" w="300" :z="10">...</Abs>
<!-- x/y/w are the prop names, NOT top/left/width -->
<VideoBlock src="/videos/demo.mp4" caption="Demo" width="80%" :controls="true" />
<PlotlyGraph filePath="/chart.json" :graphWidth="600" :graphHeight="400" />
<QRCode url="https://example.com" :size="160" caption="Scan" />
Content-bearing Vue components must be multi-line with blank lines. Write
<Block>, <Box>, <Note>, <Callout>, <ResultBox>, and <Takeaway> with the opening tag,
a blank line, the body, another blank line, and the closing tag. Do this even when
the body is a single short sentence or a tiny demo cell; <Takeaway> is not an
exception. The one-line form is syntactically possible, but it is not the style for
this skill and it fails as soon as the body grows markdown syntax (bold **...**,
lists, links, footnotes [^x], inline/block math, embedded HTML, etc.). In those
cases Slidev/markdown-it treats the body as raw HTML: **foo** keeps the literal
asterisks and [^1] is not turned into a footnote reference. One-line bodies also
bypass some theme typography hooks, so the rendered size can differ from the
normal component body.
Correct (multi-line + blank lines):
<Block title="Definition">
A **convex** function satisfies[^1] $f(\lambda x + (1-\lambda) y) \le \lambda f(x) + (1-\lambda) f(y)$.
</Block>
Wrong (all on one line โ not accepted, even if the text looks simple):
<Block title="Definition">A **convex** function satisfies[^1] $f(...) \le ...$.</Block>
Do not use one-line content components such as
<Takeaway>Key point.</Takeaway>, <Block title="A">Plain text.</Block>, or
<Note title="A">Plain text.</Note>.
<Badge> is different: it is an inline pill and should stay inline when used inside
running text.
When a content component is nested inside raw HTML such as <div>, keep the
component tag and its body below Markdown's code-block indentation threshold:
do not indent them by 4 or more spaces. Four-space indentation turns the body or
tag into <pre><code>..., which can make Vue report "Element is missing end
tag." Prefer unindented component markup inside raw HTML wrappers.
Named slots follow the same rule: <template #caption> also needs a blank line around its content, otherwise a [^x] footnote reference in the caption renders literally. For a plain-text caption the caption prop is enough; only open the slot when you need footnote references, links, or Vue components.
Before handoff, run the mechanical check on edited deck files:
node <skill>/scripts/check-component-format.mjs slides.md sections/*.md
Fix every reported one-line content component. The check intentionally ignores inline
<Badge> and self-closing media components.
Design Principles
The theme exposes many features (subtitle, dense mode, section bar, footnotes, Callout types, etc.) so a deck can match a specific need โ not so every deck must use all of them. Plain markdown on a default layout is often the strongest slide.
- Subtitle is optional. Add
subtitle: only when the title is genuinely ambiguous. (default/content are aliases โ both accept subtitle:; pick whichever name reads better in your frontmatter.)
- Don't stack components. One
<Block> or one <Callout> or one <Takeaway> reads better than all three. Reach for a component only when its semantic role fits โ wrapping every paragraph in something is a smell.
- Same-kind first; mix only for meaning. Peer items should usually share component treatment, size, and alignment. Different component types on the same slide must signal a real semantic difference: information type, reading role, or emphasis intent. If you cannot name that difference, keep the same component and adjust layout/rhythm instead.
- Align parallel items horizontally. When a slide does hold multiple instances of the same component (two
<Block>s, three <Box>es, side-by-side <Callout>s), wrap them in <Grid cols="N"> so they sit in a row, not stacked vertically. Vertical stacking reads as sequence; horizontal layout reads as comparison. For equal-height items, swap <Grid> for raw native CSS Grid (<Grid> defaults to align-items: start; native defaults to stretch).
- Use the theme rhythm first. Top-level components and media blocks already share
--ustc-component-gap, with compact/dense variants. <Note> also has dedicated --ustc-note-stack-gap / --ustc-note-after-gap tokens because title-body pairs often need slightly more breathing room than ordinary flow blocks. If a single slide still needs a touch more or less separation at one specific point, use <VSpace size="..."/> instead of <br>: small values (xs, sm, or about 0.3-0.8rem) for local breathing room; md/lg or about 1rem+ only between major zones. If a slide needs many manual spacers, the layout or component choice is probably wrong. For repeated spacing changes, override the relevant rhythm token on .slidev-layout for one slide or :root for the deck instead of sprinkling one-off spacers throughout the deck.
- Use
flowGap for body block rhythm. Set flowGap globally when the whole deck should breathe tighter/looser, or per slide when one dense page needs special spacing. It controls the same top-level flow rhythm as --ustc-component-gap; use dedicated tokens for component-specific structure such as Note stacking or figure caption spacing.
- Dense mode is for content pressure, not aesthetics. If the slide already fits in
density: normal, don't switch to dense. If the problem is page padding rather than text size, try margin: tight first.
- Toggle, don't litter. Disable
sectionBar/footer per-slide for cover, end, blank, and full-bleed visuals โ not casually elsewhere.
- Climb the precedence ladder for one-off styling: frontmatter prop โ CSS variable override in a slide
<style> block โ wrap the component in a plain <div style="โฆ">. Two scoping levels for CSS variable overrides: (a) .my-scope { --var } on a wrapper <div> โ only that subtree changes; (b) .slidev-layout { --var } โ the entire slide including h1, footer, and section bar. For deck-wide overrides, put :root { --var } in styles/index.css. If none of these fit, propose a feature request or PR against the theme repo โ never edit the installed theme files (e.g. node_modules/@luocfprime/slidev-theme-ustc/โฆ). Those files are outside project scope: they get wiped on every reinstall, the change does not version-control with your deck, and the deck silently forks from upstream.
Implementation boundaries for agents: auto-numbering only recognizes static FigureBlock/TableBlock tags; dynamic component aliases and v-bind object spreads require manual :number. subtitle: remains a frontmatter prop paired with a markdown # h1; the theme uses a small layout-local DOM relocation internally to preserve that authoring API, so do not extend subtitle behavior into cross-slide metadata inference. Typst code fences are bundled as advanced rendering support; keep them optional and do not make core theme behavior depend on Typst output.
See references/design-guide.md for fine-tuning recipes (width limits, gutter columns, scoped overrides) and a "when NOT to use" table per feature.
Content Budget
The slide canvas is 980 ร 552 px (16:9). After the section bar, h1, title gap, and padding, the usable body height is ~23.5 rem (โ 376 px). Numbers below assume 1 rem = 16 px.
Line budget
| Density | Height per line (font ร line-height) | Hard max (single-line bullets only) | Practical target |
|---|
| normal | 1.4 rem ร 1.8 = 2.52 rem / 40 px | 8 | 5โ6 |
| compact | 1.22 rem ร 1.65 = 2.01 rem / 32 px | 10 | 7 |
| dense | 1.05 rem ร 1.5 = 1.58 rem / 25 px | 13 | 9โ10 |
A "line" is one rendered text line โ a sub-bullet, a continuation wrap, or a blank-line separator all count. Use the practical target column: real bullets often wrap, and slides usually include a heading, intro sentence, or component alongside the list. Plan for 5โ6 at normal density; only approach the hard max if every item is a short single-line phrase with no other elements on the slide.
Component line cost
Add these to your line count. "Normal" = density: normal; "Dense" = density: dense.
| Element | Normal (40 px/line) | Dense (25 px/line) |
|---|
<Callout type="โฆ" title="โฆ"> + 1 body line | 3 | 2.5 |
<Block title="โฆ"> + 1 body line | 2.5 | 2 |
<ResultBox> + 1 body line | 2.5 | 2 |
<Note title="โฆ"> + 1 body line | 2 | 1.5 |
<Takeaway> (1 sentence) | 1.5 | 1.5 |
| Each extra body line inside any component | +1 | +1 |
<Grid cols="2"> row | max(left, right) + 0.5 | max(left, right) + 0.5 |
$$โฆ$$ simple (single-line, super/subscripts) | 1.5 | 2.5 |
$$โฆ$$ complex (fraction, ฮฃ/โซ, matrix) | 2โ2.5 | 3โ3.5 |
Display math does not scale down in dense mode. .katex-display uses margin: 1em inherited from .slidev-layout (1.1 rem = 17.6 px, fixed), and KaTeX renders at 1.21 ร that size โ neither is overridden by density: dense. The same physical pixel height therefore represents more line equivalents at dense density. A slide with two display-math blocks plus bullets can overflow at dense density even if the raw line count looks safe.
Image sizing
Images in <FigureBlock> do not auto-shrink to the available body height โ they render at full width by default (max-height: 38rem CSS cap). Control size via props:
| Situation | Approach |
|---|
| Limit width (most common) | width="60%" on <FigureBlock> โ constrains the whole figure block |
| Limit image width inside the block | imageWidth="400" (px) or imageWidth="80%" |
Figure in a <Grid cols="2"> or split column | no extra sizing needed โ the column constrains it |
<FigureBlock src="/img.png" caption="โฆ" width="60%" />
<FigureBlock src="/img.png" caption="โฆ" imageWidth="400" />
width sets the <figure> element's width (centering in the full column). imageWidth sets only the <img> inside it. Use width when the caption should also be narrowed; use imageWidth when you want the caption to span the full column width.
Overflow rules
Check line count before writing, not after.
- If over budget at normal density by โค 20%: switch to
density: dense + margin: tight.
- If over budget by more than 20%, or if there are โฅ 2 visually distinct ideas: split into two slides โ add
(1/2) / (2/2) to subtitle:.
- Never reach for
density: dense to cram content that genuinely belongs on two slides; dense mode is a slight scale-down, not a compression tool.
- When splitting, put the core claim on slide 1 and the supporting detail on slide 2 (or demote to a
backup slide).
CSS Variables
Full variable reference: references/api/theme-tokens.md.
Key overridable variables:
| Variable | Default | Role |
|---|
--ustc-blue | #1E4C90 | primary brand color |
--ustc-fs-body | 1.4rem | body text (normal density) |
--ustc-fs-body-compact | 1.22rem | body text (compact density) |
--ustc-fs-body-dense | 1.05rem | body text (dense density) |
--ustc-fs-callout / --ustc-fs-callout-title | Block scale | Callout body/title |
--ustc-fs-result-title / --ustc-fs-result-body | Block scale | ResultBox title/body |
--ustc-fs-block-title / --ustc-fs-block-body | 1.22rem / 1.22rem | Block title/body |
--ustc-fs-note-title / --ustc-fs-note-body | body scale | Note title/body |
--ustc-fs-takeaway | 1.4rem | Takeaway text |
--ustc-fs-badge-scale | 0.68 | Badge text scale vs body |
--ustc-fs-badge | unset | Fixed Badge text override |
--ustc-lh | 1.8 | body text line-height |
--ustc-title-gap | 1.5rem | h1 to first body element gap |
--ustc-component-gap | 0.75rem | top-level content block gap |
--ustc-note-stack-gap / --ustc-note-after-gap | 0.95rem / 1rem | Note-to-Note / Note-to-next gap |
--ustc-px / --ustc-py | 2.8rem / 1.75rem | slide padding |
--ustc-max-w-cover-h1 / --ustc-max-w-cover-sub | 48rem / 58rem | cover title/subtitle width |
--ustc-footer-h | 1.75rem | footer bar height |
Common Patterns
Dense slide (lots of content)
---
layout: content
density: dense
margin: tight
---
density: dense is a coordinated scale-down โ it simultaneously shrinks body text (1.4rem โ 1.05rem), Note title/body with the body scale, Callout / ResultBox / Block text on the same component scale (1.22rem โ 1.05rem), table cells (1.1rem โ 0.96rem), h2 (drops to h3 size 1.3rem), Takeaway text (1.4rem โ 1.05rem), and tightens line-height (1.8 โ 1.5) plus list spacing. Use it instead of overriding font sizes in <style> because a <style> override changes one element in isolation and breaks the theme's internal proportions. Dense mode keeps the whole slide visually coherent at a smaller scale. Combine with margin: tight or margin: tighter to reclaim additional page padding.
density: compact is the middle tier โ every typography token sits roughly halfway between normal and dense (body 1.4rem โ 1.22rem โ 1.05rem; line-height 1.8 โ 1.65 โ 1.5). Reach for it when a slide is slightly over budget at normal but dense shrinks things more than necessary. Same coordinated-scale mechanism as dense โ don't hand-tune font sizes to fake an in-between size.
Limit a component's width
Wrap the component in a plain <div style="โฆ"> โ inline style on a raw <div> is bulletproof. Don't try to pass style or class directly to a theme component in markdown: Slidev's <style> blocks are auto-scoped (so .my-class on the component root often doesn't match), and attribute passthrough on theme components in markdown is unreliable.
<Grid cols="2" gap="lg">
<Block title="A">
โฆ
</Block>
<div style="justify-self: center; max-width: 26rem;">
<Block title="B">
โฆ
</Block>
</div>
</Grid>
For consistent inset width across slides, prefer gutter columns: <Grid cols="1 8 8 1"> with empty <div>s on the edges.
For equal-height items, replace the theme <Grid> with raw native CSS Grid โ <Grid> defaults to align-items: start, native Grid defaults to stretch:
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.4rem;">
<Block>
โฆ
</Block>
<Callout type="tip">
โฆ
</Callout>
<ResultBox>
โฆ
</ResultBox>
</div>
Two-column with unequal widths
---
layout: split
ratio: "3:2"
gap: lg
---
::left::
Main content
::right::
Supporting figure or code
TOC with section highlight
---
layout: toc
variant: arrow
highlight: 2
---
Appendix slides
---
layout: backup
---
All slides after this use A.N footer numbering.
Figure + caption side by side
<Grid cols="1 1" gap="md" alignY="center">
<FigureBlock src="/img/a.png" caption="Method A" width="100%" />
<FigureBlock src="/img/b.png" caption="Method B" width="100%" />
</Grid>
Footnotes in flow (not overlaid)
---
footnote: flow
---
Content with footnote[^1]
[^1]: This appears in document flow, not overlaid at the bottom.
Override section label for TOC
---
layout: section
sectionLabel: 'Short Name'
---
The sectionLabel value is used in the TOC and section bar; the h1 is shown on the section slide itself.
Footer Modes
Full mode (default): author ยท title ยท meeting ยท date ยท page/total
Minimal mode: author ยท page/total
Set globally:
footerMode: minimal
Or per-slide to override:
---
footerMode: minimal
---
Section Bar
The section bar shows the presentation structure at the top of body slides. Only content / default / split slides render the bar; cover, end, toc, section, blank, and backup slides never show it (so sectionBar: false only matters on body layouts).
- Full mode: section labels + progress indicator
- Minimal mode: progress indicator only
- Labels mode: section labels only
The "progress indicator" is per-slide dots by default. When a section has more dots than fit on one row, that section's indicator automatically switches to a horizontal progress bar (click anywhere on it to jump to the corresponding slide). Other sections keep their dots. The threshold is computed from the actual available width per section, so wide decks with few sections get dots; dense decks with many sections may show progress bars in the long sections.
Control globally:
sectionBarMode: full
sectionBar: false
Hide on specific slides:
---
sectionBar: false
---
The bar height is controlled by two overridable CSS variables (set these in your deck's styles/index.css):
:root {
--ustc-nav-h-full: 2rem;
--ustc-nav-h-minimal: 1.5rem;
}
--ustc-nav-h is computed automatically from these and must not be set manually.
WIP Markers
Two independent WIP signals exist; they do not interact:
Component-level wip prop โ on <FigureBlock>, <TableBlock>, <VideoBlock>, <QRCode>. Shows a red "WIP" badge on the component itself. No effect on section-bar dots.
Slide-level wip: true frontmatter โ on any body slide (content, default, split). Produces two effects:
- A large semi-transparent "WIP" watermark centered on the slide.
- In the section bar: if the section is showing dots, that slide's dot is tinted red with a pulsing glow. If the section is showing a progress bar (because dots overflowed), the whole bar tints red and pulses to signal "this section contains WIP content"; per-slide WIP precision is intentionally not preserved in progress-bar mode.
---
layout: content
wip: true
---
Scope limitation: wip: true on a section layout slide shows the watermark, but does not produce a red dot in the section bar. Section slides appear as section-group titles in the bar, not as body-slide dots. If you need a WIP signal on a section slide visible in the bar, add a placeholder body slide after it with wip: true.
Quick Reference
| Goal | How |
|---|
| Smaller body text globally | :root { --ustc-fs-body: 1.2rem } in styles/index.css, or .slidev-layout { --ustc-fs-body: 1.2rem } per slide |
| Dense text on one slide | density: dense in frontmatter |
| Middle density (between normal & dense) | density: compact in frontmatter |
| Deck-wide body line-height | lineHeight: 1.6 in first-slide global frontmatter; override per body slide with lineHeight |
| Deck-wide top-level block spacing | flowGap: tight in first-slide global frontmatter; override per body slide with flowGap: 0.6rem |
| Change h1 / heading colour per slide | .slidev-layout { --ustc-blue-dark: #... } in slide <style> โ reaches h1, block/takeaway/callout titles |
| Recolor section bar + footer per slide | .slidev-layout { --ustc-blue: #... } in slide <style> โ both chrome bars share this token |
| Recolor only the footer (keep nav blue) | .slidev-layout { --ustc-footer-bg: #... } in slide <style> |
| Hide section bar on one slide | sectionBar: false in frontmatter |
| Indicator-only section bar (no labels) | sectionBarMode: minimal |
| Labels-only section bar (no indicator) | sectionBarMode: labels |
| Change section bar height | --ustc-nav-h-full / --ustc-nav-h-minimal in :root (labels mode uses minimal height) |
| Mark a body slide as WIP | wip: true in frontmatter (watermark + red section-bar dot) |
| Background image / color on any layout | background: '/bg.jpg' (or '#1a2a4a') in frontmatter โ accepted by every layout |
| Add overlay to a background image | .slidev-layout { background-image: linear-gradient(rgba(255,255,255,0.88), rgba(255,255,255,0.88)), url('/bg.jpg') } in slide <style> |
| Custom figure/table prefix | figurePrefix: "Fig." / tablePrefix: "Tab." in global frontmatter |
| Custom number suffix | figureNumberSuffix: ": " / tableNumberSuffix: ": " globally, or numberSuffix=": " per block |
| Click-to-zoom figures (lightbox) | figureZoom: true in global frontmatter (deck-wide); per-figure zoomable / :zoomable="false" override. Interactive-only โ no-op in PDF export. |
| Layer Abs elements | :z="20" on top, :z="10" behind |
| Wider left column in split | ratio: "3:1" |
| Inline footnotes | footnote: flow |
| One-off width / spacing tweak | Wrap the component in <div style="โฆ">; don't passthrough to the theme component |
| Equal-height items | Use raw native CSS Grid instead of <Grid> โ see design-guide.md |
| Absolute positioning | Use <Abs x y w :z> (%/px coords); Slidev's built-in <v-drag> also works for drag-positioned elements |
| All valid prop values | See references/api/components.md |
| All CSS variables | See references/api/theme-tokens.md |
| Re-enable typographic replacements | slidev: { markdown: { markdownOptions: { typographer: true } } } in deck's vite.config.js |
| Allow external network access to dev server | server: { allowedHosts: true } in deck's vite.config.ts |
| Ignore speaker-note-only HMR updates | Built into the theme for slides.md and sections/*.md; add deck-specific files via ustcTheme.extraNotesOnlyHmrFiles |
Vite Configuration
Use the deck's vite.config.js only for deck-local server and markdown options.
Do not copy files from the theme repo's vite.config.ts into a deck; reusable
theme behavior must come from the installed theme package itself. If a theme bug
requires changing setup/, styles/, layouts/, components/, or utils/,
update/reinstall the theme package and restart Slidev.
A deck only ever has one export default โ merge everything into a single object:
export default {
server: {
allowedHosts: true,
},
slidev: {
markdown: {
markdownOptions: { typographer: true },
},
},
}
Include only the keys you actually need โ the two blocks above are independent and can be used separately.
Theme-maintained HMR whitelist
The theme's default Vite plugin ignores HMR when a whitelisted markdown file
changes only Slidev speaker notes (HTML comments outside fenced code blocks).
This keeps presenter-note edits from refreshing the visible deck.
Default whitelist:
export const notesOnlyHmrFiles = ['slides.md', 'sections/*.md']
For a deck/project with extra markdown entry points, do not copy the theme
plugin into the project. Add the extra deck-root-relative patterns in the deck's
vite.config.js:
export default {
ustcTheme: {
extraNotesOnlyHmrFiles: ['chapters/*.md', 'appendix.md'],
},
}
If the deck also needs other local Vite options, merge them into the same single
export default object:
export default {
server: {
allowedHosts: true,
},
ustcTheme: {
extraNotesOnlyHmrFiles: ['chapters/*.md'],
},
}
For theme-repo changes that should affect every deck by default, update
notesOnlyHmrFiles in setup/vite-plugins.ts instead:
export const notesOnlyHmrFiles = ['slides.md', 'sections/*.md', 'chapters/*.md']
Patterns are deck-root-relative, case-insensitive, and support * inside a
single path segment only. Do not add broad patterns such as **/*.md unless the
theme intentionally wants notes-only edits in every markdown file to skip HMR.
After changing the whitelist or stripping behavior, update
tests/vitePlugins.test.mjs and run:
pnpm exec tsx --test tests/vitePlugins.test.mjs
pnpm test:unit
Additional Resources
- references/authoring-academic-decks.md โ how to design good academic decks: argument spine, layout decision tree, density rubric, figure handling, academic taboos, citations, the renderโmeasureโfix audit loop
- references/scenarios/group-progress.md โ internal group meeting / weekly progress defaults, including no-affiliation covers and WIP handling
- references/scenarios/group-literature.md โ literature survey / paper reading structure, metadata badges, and survey-specific pitfalls
- references/scenarios/conference-talk.md โ formal conference/oral paper talks: affiliations, time budget, image-heavy text-light slides, and drafted PPT preservation
- references/workflows/ppt-to-slidev.md โ classify PPTs as materials-only vs drafted deck, then convert appropriately
- references/workflows/asset-ingestion.md โ bring in PDF/SVG/raster/video assets; includes
scripts/pdf2svg.py
- references/workflows/handover-export.md โ package PDF/PPTX/PNG/source handovers; includes
scripts/handover-export.sh
- references/style/wording.md โ wording preferences: avoid AI-smelling shorthand, invented acronyms, filler, and unsupported SOTA claims
- references/style/visual-rubric.md โ operational check for sparse, cramped, ugly, over-boxed, or misaligned slides
- scripts/audit-layout.mjs โ the layout audit ruler: renders each slide headless and reports overflow / sparse / wide-figure-in-split / image whitespace / orphan defects with fix hints (
node <skill>/scripts/audit-layout.mjs <url-or-deck.md>)
- scripts/pdf2svg.py โ convert PDF figures/pages to SVG (
uv run <skill>/scripts/pdf2svg.py input.pdf -o public/images)
- scripts/handover-export.sh โ export PDF/PPTX/PNG once and zip a handover package (
bash <skill>/scripts/handover-export.sh <deck-dir> <handover-dir> <artifact-prefix>)
- scripts/check-component-format.mjs โ fail on one-line content components such as
<Takeaway>...</Takeaway> (node <skill>/scripts/check-component-format.mjs slides.md sections/*.md)
- references/api/components.md โ full prop tables for all components
- references/api/prop-defaults.md โ all prop default values (source:
utils/defaults.ts)
- references/api/theme-tokens.md โ complete CSS variable reference
- references/examples/full-deck.md โ canonical demo deck showing all layouts and components in use
- references/design-guide.md โ design principles, layout fine-tuning recipes, "when NOT to use" table per feature
Runnable demo decks under references/examples/ (each runs with slidev examples/<name>.md):