MastriaDocs

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 :root and every place that reads it follows. To give a value per mode, write light-dark(<light>, <dark>): the academy sets color-scheme to 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-only classes 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 to https:// addresses or inline data:; no expression(), 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.

TokenWhat it paints
--canvasThe page background
--paperCards, the header, raised surfaces
--sunkenWells inside cards: code blocks, the lab editor, the course hero band
--washHover tint on rows and buttons
--line, --line-subtle, --line-strongBorders, from quiet to firm
--ink, --ink-muted, --ink-subtleText, from headings to fine print
--accentButtons, progress bars, selected chips
--accent-hoverA button under the pointer
--on-accentText on an accent-filled control
--accent-textLinks and accent-colored words
--accent-tint, --accent-lineSoft accent panels and their borders
--ringThe focus ring
--ok, --ok-tint, --ok-linePassed, complete
--bad, --bad-tint, --bad-lineFailed, error
--warn, --warn-tint, --warn-lineAttention
--info, --info-tint, --info-lineNotes

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

TokenWhat it sets
--font-textBody text
--font-displayHeadings (h1–h3)
--font-codeCode blocks, inline code, the lab editor
--radius-sm, --radius-md, --radius-lg, --radius-xl, --radius-2xlCorners, 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.

LandmarkWhere
headerThe site header bar
header-logoThe link holding your logo or academy name
header-navYour 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-searchThe search trigger
header-accountThe Sign in button or the account avatar
footerThe page footer
homeThe academy home page
home-sectionOne 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
bannerA Banner row (Home workspace): its card
banner-bodyThe banner's title, text and button
banner-ctaThe banner's button
banner-mediaThe banner's image
banner-personThe banner's portrait, name and role
tilesA Cards row: the heading and the grid
tileOne card of a Cards row
tile-buttonA card's button
html-blockAn HTML block row: the element holding your markup (rendered with the lesson typography; your classes are kept)
course-cardA course card (the featured one also has data-hero); data-card-style is cover or compact (Brand → Cards)
path-cardA learning-path card
course-heroThe course page's top band
course-ctaThe course page's Start / Continue / Review button
course-outlineThe course page's syllabus column
course-ratingThe 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-reportA lesson's Report a problem line in its footer, and its form when open
lessonThe lesson page
lesson-bodyThe lesson's content column (prose, labs, quizzes)
lesson-outlineThe course outline (the lesson page's rail and the course page's syllabus)
lesson-pagerPrevious / next lesson links
course-finishedThe lesson footer's "Course complete" line, shown once every lesson is done
lab-benchA lab inside a lesson (browser-run or embedded); data-adapter names which
lab-setupA browser lab's Defined for you disclosure: the setup code that runs before the learner's
quizA quiz block inside a lesson
progress-barAny progress bar
certificateThe certificate document
profileThe learner's profile page
catalog-pageA catalog's page
all-courses-pageThe All courses page (/courses)
all-paths-pageThe All learning paths page (/paths)
all-catalogs-pageThe All catalogs page (/catalogs)
catalog-cardA catalog's card (All catalogs)
course-browserThe filter sidebar and the cards beside it (All courses, and catalog pages with eight or more cards)
filter-sidebarThe sidebar's groups: the column on wide screens, and the full-screen sheet behind Filters on phones
filter-groupOne group of the sidebar (Level, a group of tags…)
filter-resultsThe search box, the result count with the active filters, and the cards
path-pageA learning path's page

Classes

Controls share classes you can target without knowing their utility class names:

ClassWhat it is
.m-buttonAny button-shaped control
.m-button-primaryThe accent-filled button (Start course, Check my work, Save)
.m-button-outlineThe quiet bordered button
.m-cardA card surface
.m-chipA pill: topic filters, the New badge
.m-chip-activeA selected pill
.m-tagA small grey attribute tag
.m-badgeThe amber attention badge
.m-inputA text field or select
.m-selectA 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-titleA learner page's title
.m-row-headingA section heading on a learner page
.m-tableA data table (the training history)

Examples

A dark navy header with white links, in both modes:

CSS
[data-part="header"] {
  background: #0b1d3a;
  border-bottom-color: #0b1d3a;
}
[data-part="header"] a,
[data-part="header"] button {
  color: #ffffff;
}

Rounder, bolder buttons everywhere:

CSS
.m-button {
  border-radius: 999px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

Your own display face for headings, and a warmer light-mode page:

CSS
@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.