Custom CSS
You never have to write CSS: Admin → Brand turns one color, your logos and your type into a readable academy in light and dark. When you want more than that — a different header, rounder cards, your product's exact button — Admin → Brand → Custom CSS takes a stylesheet of your own.
This page is the reference it's written against. Everything named here is a contract: it stays stable through every platform release, and a test in our build fails if any of it changes. Anything not named here (the class names you see in the page source, the order of elements inside a card) may change without notice, so a rule that targets it can stop working. Write against the reference and your stylesheet keeps working.
Tip
Paste this whole page into your AI assistant with what you want ("make the header dark navy with white links, and round every button"), and ask for CSS that uses only these tokens, landmarks and classes. Then paste the result into the Custom CSS page and preview it on your academy.
How it applies
- Learner pages only. Your stylesheet loads on every page of the academy learners see — home, courses, lessons, labs, certificates, the profile, shared progress reports — after the academy's own theme. The admin never wears it, and neither does the sign-in page (it is shared platform chrome that already carries your brand).
- It wins by default. Most of the academy's own styles live in CSS
cascade layers, and your stylesheet loads last, unlayered, so a rule of
yours beats the academy's at equal specificity without
!important. - Light and dark through
light-dark(). The academy's palette is a set of CSS variables that already swap for dark mode. Set a token on:rootand every place that reads it follows. To give a value per mode, writelight-dark(<light>, <dark>): the academy setscolor-schemeto whichever mode is in effect, so the function follows the reader's switch in the header and an academy locked to one mode. For showing an element in one mode only, the.theme-light-only/.theme-dark-onlyclasses do the same job. Avoid hard-coded colors on text or backgrounds unless you give both modes. - Draft, preview, publish. Save draft keeps your work as you go. Preview the draft on the academy shows it to you alone on the real pages (a banner says so); learners keep seeing the published stylesheet until you press Publish draft (an empty draft offers Remove custom CSS instead). Every publish is kept; History lists the last 20, and any of them is one click (Load as draft) from becoming the draft again.
- Limits. Up to 64 KB. No
@import;url()only tohttps://addresses or inlinedata:; noexpression(),behavior:or-moz-binding. A save that breaks a rule tells you the line.
Warning
Don't use @media (prefers-color-scheme: dark). It reads the device,
not the academy, and lands your dark values under light ink (or the
reverse) whenever the two differ — a learner who switched to Light in
the header, or an academy locked to one mode. light-dark() is the
mode-aware spelling.
Color tokens
Set on :root, with light-dark() for a value per mode. Every
component reads these; a brand color set on the Brand page already
derives them all, so override only what you want different.
| Token | What it paints |
|---|---|
--canvas | The page background |
--paper | Cards, the header, raised surfaces |
--sunken | Wells inside cards: code blocks, the lab editor, the course hero band |
--wash | Hover tint on rows and buttons |
--line, --line-subtle, --line-strong | Borders, from quiet to firm |
--ink, --ink-muted, --ink-subtle | Text, from headings to fine print |
--accent | Buttons, progress bars, selected chips |
--accent-hover | A button under the pointer |
--on-accent | Text on an accent-filled control |
--accent-text | Links and accent-colored words |
--accent-tint, --accent-line | Soft accent panels and their borders |
--ring | The focus ring |
--ok, --ok-tint, --ok-line | Passed, complete |
--bad, --bad-tint, --bad-line | Failed, error |
--warn, --warn-tint, --warn-line | Attention |
--info, --info-tint, --info-line | Notes |
The status colors (--ok, --bad, --warn, --info) are the same in
every academy on purpose: green means passed everywhere. Change them
only with a reason.
Type and shape tokens
| Token | What it sets |
|---|---|
--font-text | Body text |
--font-display | Headings (h1–h3) |
--font-code | Code blocks, inline code, the lab editor |
--radius-sm, --radius-md, --radius-lg, --radius-xl, --radius-2xl | Corners, from fields and chips (sm, md) through buttons and inputs (lg) to cards (xl, 2xl) |
To use a font that isn't on the Brand page's list, declare an
@font-face with an https:// source and point a type token at it.
Landmarks
Structural parts of the page carry a data-part attribute. Target them
as [data-part="header"]. They are stable; the elements inside them are
not, so style the landmark, or descendants by the classes below.
| Landmark | Where |
|---|---|
header | The site header bar |
header-logo | The link holding your logo or academy name |
header-nav | Your header links (Brand → Header and footer); data-layout is start or center. A link with an icon renders the icon as an inline svg before its label |
header-search | The search trigger |
header-account | The Sign in button or the account avatar |
footer | The page footer |
home | The academy home page |
home-section | One row of the home; data-section-type names it: continue_row, hero, catalog_row, topic_row, paths_row, new_row, announcement, all_courses, banner, cards, html |
banner | A Banner row (Home workspace): its card |
banner-body | The banner's title, text and button |
banner-cta | The banner's button |
banner-media | The banner's image |
banner-person | The banner's portrait, name and role |
tiles | A Cards row: the heading and the grid |
tile | One card of a Cards row |
tile-button | A card's button |
html-block | An HTML block row: the element holding your markup (rendered with the lesson typography; your classes are kept) |
course-card | A course card (the featured one also has data-hero); data-card-style is cover or compact (Brand → Cards) |
path-card | A learning-path card |
course-hero | The course page's top band |
course-cta | The course page's Start / Continue / Review button |
course-outline | The course page's syllabus column |
course-rating | The course page's rating: the ask in the "Course complete" card, or Rate this course and the learner's own rating in the header |
lesson-report | A lesson's Report a problem line in its footer, and its form when open |
lesson | The lesson page |
lesson-body | The lesson's content column (prose, labs, quizzes) |
lesson-outline | The course outline (the lesson page's rail and the course page's syllabus) |
lesson-pager | Previous / next lesson links |
course-finished | The lesson footer's "Course complete" line, shown once every lesson is done |
lab-bench | A lab inside a lesson (browser-run or embedded); data-adapter names which |
lab-setup | A browser lab's Defined for you disclosure: the setup code that runs before the learner's |
quiz | A quiz block inside a lesson |
progress-bar | Any progress bar |
certificate | The certificate document |
profile | The learner's profile page |
catalog-page | A catalog's page |
all-courses-page | The All courses page (/courses) |
all-paths-page | The All learning paths page (/paths) |
all-catalogs-page | The All catalogs page (/catalogs) |
catalog-card | A catalog's card (All catalogs) |
course-browser | The filter sidebar and the cards beside it (All courses, and catalog pages with eight or more cards) |
filter-sidebar | The sidebar's groups: the column on wide screens, and the full-screen sheet behind Filters on phones |
filter-group | One group of the sidebar (Level, a group of tags…) |
filter-results | The search box, the result count with the active filters, and the cards |
path-page | A learning path's page |
Classes
Controls share classes you can target without knowing their utility class names:
| Class | What it is |
|---|---|
.m-button | Any button-shaped control |
.m-button-primary | The accent-filled button (Start course, Check my work, Save) |
.m-button-outline | The quiet bordered button |
.m-card | A card surface |
.m-chip | A pill: topic filters, the New badge |
.m-chip-active | A selected pill |
.m-tag | A small grey attribute tag |
.m-badge | The amber attention badge |
.m-input | A text field or select |
.m-select | A select box (it carries .m-input too). Its chevron is an icon beside it, in --ink-muted; set color on .m-select + svg to recolor it |
.m-page-title | A learner page's title |
.m-row-heading | A section heading on a learner page |
.m-table | A data table (the training history) |
Examples
A dark navy header with white links, in both modes:
[data-part="header"] {
background: #0b1d3a;
border-bottom-color: #0b1d3a;
}
[data-part="header"] a,
[data-part="header"] button {
color: #ffffff;
}
Rounder, bolder buttons everywhere:
.m-button {
border-radius: 999px;
font-weight: 600;
letter-spacing: 0.01em;
}
Your own display face for headings, and a warmer light-mode page:
@font-face {
font-family: "Acme Display";
src: url(https://cdn.acme.dev/fonts/acme-display.woff2) format("woff2");
font-display: swap;
}
:root {
--font-display: "Acme Display";
--canvas: light-dark(#fbf8f3, #141210);
}
What stays out
- JavaScript. A stylesheet can't read a learner's session or change what a page records; a script could. Scripts stay out, so every academy's labs, progress and certificates stay trustworthy. Integrations go through webhooks and the API — see Integrations & exports.
- The admin. Staff screens keep the platform's own look so states read the same in every academy. Your brand shows in their previews.
For rows of your own on the home — a banner, a strip of cards, or a block of sanitized HTML your stylesheet can target — see Home & catalogs.