Connect your AI: Standard plans now include all Slate MCP tools. Try Slate free for 1 month. Offer ends June 13.Try Slate free for 1 month

See plans

The complete guide to custom themes

Match your brand, build a dark mode, or design from a vibe. Slate's theming goes as deep as you want it to.

Describe a vibe. Paste the prompt. Import the result.

How theming works

Slate's theming operates on two layers:

  1. Theme Settings: High-level controls for colors, fonts, spacing, and border radius. These are configured through the Theme page in the builder and stored as part of your course data.
  2. Custom CSS: A full CSS editor that lets you override any visual aspect of the player. This is where you can create dramatic dark themes, gradients, glassmorphism effects, and more.

The settings layer is ideal for quick brand alignment. Custom CSS is where the real creative power lives.

Under the hood, both layers work through CSS custom properties (variables). When you set a primary color in the theme settings, Slate sets --primary-color on the player's root element. Custom CSS can override these variables and target any player element directly.

Already part-way there. If you signed up with a website URL, Slate pulled your brand colours and logo from your site during onboarding, so your first course already carries your palette. This page is for taking that further — or starting from scratch with a completely custom look.

Generate a theme with AI

The fastest path to a custom Slate theme: describe the look you want, paste this prompt into Claude or ChatGPT, then import the result on the Theme page. Refine in the editor afterward.

The prompt teaches the model Slate's export schema, CSS variable system, and full selector map, so you get a theme JSON and matching Custom CSS that's ready to import.

AI theme generation prompt
I want you to create a custom theme for Slate (an eLearning course builder). Generate a complete theme configuration that I can import.

**My desired theme:** [Describe your theme here, e.g., "A warm, earthy dark theme with amber/gold accents, inspired by a cozy library. Use rounded corners and elegant serif headings."]

---

## OUTPUT FORMAT

Produce TWO outputs:

### 1. Theme Settings JSON

A JSON object in this exact format:

```json
{
  "_slateThemeExport": true,
  "exportVersion": "1.0",
  "exportedAt": "[current ISO date]",
  "theme": {
    "primaryColor": "#HEXCOLOR",
    "primaryForegroundColor": "",
    "hoverColor": "#HEXCOLOR",
    "outlineColor": "#HEXCOLOR or rgba()",
    "outlineThickness": 1,
    "borderRadius": 8,
    "spacing": 16,
    "headingFont": "font-id",
    "bodyFont": "font-id",
    "headingFontWeight": 700,
    "bodyFontWeight": 400,
    "customCss": "[full CSS from output 2 as a JSON string — escape newlines as \n and double-quotes as \". The string can be long; max 50,000 characters.]",
    "logoUrl": "",
    "navigationLayout": "classic",
    "contentLayout": "standard",
    "lockedNavigation": false,
    "assessmentAutoscroll": true,
    "showExitCourse": false,
    "knowledgeChecks": {
      "maxAttempts": 0,
      "revealCorrectAnswer": true,
      "revealAnswersPerAttempt": false,
      "showFeedback": true,
      "eliminateWrongOptions": false
    }
  }
}
```

**Field notes** (so the model fills these in correctly):
- `primaryForegroundColor` — text colour on primary buttons and active nav. Leave as empty string `""` to let Slate auto-derive (light text on dark buttons, dark text on light ones). Set explicitly only when you want a specific brand pairing.
- `logoUrl` — empty string is fine; logos are uploaded via the builder rather than embedded in theme JSON.
- `contentLayout` — `"standard"` keeps text in a comfortable reading column; `"wide"` gives images, videos, tables, and multi-column blocks edge-to-edge room with a hamburger nav. Pick `"wide"` for visually heavy or editorial-style courses.
- `knowledgeChecks` — course-wide defaults for retry behaviour and feedback on knowledge checks. `maxAttempts: 0` means unlimited.

### 2. Custom CSS

The complete CSS to paste into the Custom CSS editor.

---

## CONSTRAINTS

**Color formats:** Use hex (#XXXXXX) or rgba() only. No named colors, hsl(), or 3-digit hex.

**Value ranges:**
- outlineThickness: 0-4 (integer)
- borderRadius: 0-24
- spacing: 0-32
- Font weights: 100-900 in steps of 100

**Blocked CSS patterns (these will be stripped):**
- @import
- javascript: in url()
- expression()
- behavior:
- </style>

**Available font IDs (pick from these only):**

Sans-serif: inter, open-sans, roboto, lato, poppins, nunito, work-sans, dm-sans, source-sans-3, noto-sans, mulish, rubik

Serif: playfair-display, merriweather, lora, source-serif-pro, crimson-pro, libre-baskerville, bitter

Display: montserrat, raleway, oswald, bebas-neue, archivo, sora

Handwriting: caveat, dancing-script, pacifico

Monospace: fira-code, jetbrains-mono

---

## CSS VARIABLE SYSTEM

The player uses CSS custom properties on :root. Override these to control the theme:

**Color Scale (the foundation - invert for dark themes):**
--slate-50 through --slate-900

Default light values: #F8FAFC, #F1F5F9, #E2E8F0, #CBD5E1, #94A3B8, #64748B, #475569, #334155, #1E293B, #0F172A

**Semantic colors:**
--primary-color, --primary-foreground, --accent-color, --accent-hover, --accent-light
--outline-color, --outline-thickness
--success / --success-light, --error / --error-light

`--primary-foreground` is the text colour drawn on top of `--primary-color` (buttons, active nav). Slate auto-derives a readable colour from luminance when `primaryForegroundColor` is left empty.

**Typography:**
--font-family-heading / --font-family-body
--font-weight-heading / --font-weight-body

**Spacing:** --space-1 (0.25rem) through --space-16 (4rem)
**Border radius:** --radius-sm through --radius-2xl, --radius-full

---

## TARGETABLE SELECTORS

### Shell
body, #slate-player, #player-content

### Header
#player-header, #course-title, #progress-bar, #progress-fill, #progress-text

### Navigation Sidebar
#player-nav, .nav-header, .nav-header-title, .nav-section-title
.nav-lesson, .nav-lesson:hover, .nav-lesson.active, .nav-lesson.viewed:not(.active)::after

### Footer
#player-footer, #btn-prev, #btn-next

### Text Content
.block-text, .block-text h1/h2/h3/h4, .block-text a, .block-text code, .block-text blockquote

### Buttons
.button-primary/.slate-button.button-primary, .button-secondary, .button-outline (+ :hover)

### Media
.block-image figure / img / figcaption
.block-video .video-wrapper, .block-video .video-caption
.audio-block, .audio-block audio, .audio-block figcaption  (note: the audio block's class is "audio-block", NOT "block-audio")
.block-iframe .iframe-wrapper

### Interactive Blocks
.block-accordion, .block-tabs, .tab-button.active
.card, .card-style-*, .flip-card-face, .carousel-card, .carousel-dot.active

### Tables
.slate-table th/td, .slate-table thead th, .slate-table.table-stripe-*

### Knowledge Checks
.kc-option, .kc-option.selected, .kc-option.correct/.incorrect, .kc-submit, .kc-feedback

### Assessment
.assessment-intro, .assessment-results, .assessment-results.passed/.failed
.assessment-score-circle, .assessment-start-btn, .assessment-submit-btn

---

## TIPS FOR GREAT THEMES

1. Dark themes: Invert the --slate-* scale (50=darkest, 900=lightest)
2. Use var() references instead of hardcoding colors
3. Gradients on #progress-fill, #course-title, .button-primary, .nav-lesson.active
4. Glassmorphism: backdrop-filter: blur(12px) + rgba() backgrounds on header/footer
5. Glow effects: box-shadow: 0 0 Npx rgba(accent, 0.3-0.5) on active elements
6. Gradient text: background: linear-gradient(...), -webkit-background-clip: text
7. Always style both .button-primary AND .slate-button.button-primary
8. Match scrollbar styling to your color scheme
9. Test all block types: accordions, tabs, knowledge checks, cards, tables, assessments

Now generate the theme based on my description above.
How to use it: copy the prompt, replace the bracketed description near the top with your own style brief (think mood, accent colour, typography, layout), then paste the whole thing into your AI chat. The model returns a theme JSON you can save as a .json file and import from Theme > Import.

Theme settings reference

These properties are configured through the Slate builder's Theme page. They're also included when you export/import a theme JSON file.

Colors

PropertyTypeRangeDefaultDescription
primaryColorHex or RGBAAny valid color#18181BPrimary brand color for buttons, active states, accents
primaryForegroundColorHex or RGBAAny valid color, or emptyAuto-derivedText colour on primary buttons and active nav items. Leave empty to auto-derive from luminance (light text on dark, dark on light)
hoverColorHex or RGBAAny valid colorAuto-derivedHover state color. Leave empty for automatic derivation (10% darker)
outlineColorHex or RGBAAny valid color#E4E4E7Border color for buttons and UI elements
outlineThicknessNumber0-4 px1Border width for buttons and outlined elements

Color format examples:

Color formats
#3B82F6          (hex)
#18181B          (hex)
rgba(139, 92, 246, 0.3)   (rgba with alpha)
rgb(59, 130, 246)          (rgb)

Typography

PropertyTypeRangeDefaultDescription
headingFontStringFont IDinterFont used for headings (h1-h4, lesson titles)
bodyFontStringFont IDinterFont used for body text and UI elements
headingFontWeightNumber100-900700Font weight for headings
bodyFontWeightNumber100-900400Font weight for body text

Layout

PropertyTypeRangeDefaultDescription
borderRadiusNumber0-24 px6Controls how round UI elements are (0 = sharp, 24 = very round)
spacingNumber0-32 px16Controls padding and gap scaling throughout the player
contentLayoutStringstandard or widestandardwide gives images, videos, tables, and multi-column blocks edge-to-edge room while text stays in a comfortable reading column. Picks up a hamburger nav for a modern, editorial feel

Navigation & UI

PropertyTypeOptionsDefaultDescription
navigationLayoutStringclassic or verticalclassicClassic = footer bar with prev/next. Vertical = inline next button
showScrollIndicatorBooleantrue/falsefalseShows a scroll-down indicator when content extends below the fold
enableSearchBooleantrue/falsetrueEnables the search box in the player sidebar
lockedNavigationBooleantrue/falsefalseRequires learners to complete lessons in sequence
assessmentAutoscrollBooleantrue/falsetrueWhether assessments scroll to the next question automatically after each answer. Toggle off if you want learners to scroll manually
showExitCourseBooleantrue/falsefalseShows an explicit “Exit course” control in the player chrome

Knowledge check defaults

Course-wide defaults for knowledge check behaviour, configured in Theme > Knowledge Checks. Individual questions can still override these. Stored as a knowledgeChecks object on the theme.

PropertyTypeDescription
maxAttemptsInteger ≥ 0How many times a learner can retry a question. 0 means unlimited
revealCorrectAnswerBooleanShow the correct answer after the learner exhausts their attempts
revealAnswersPerAttemptBooleanReveal which options are right or wrong after every attempt, not only the final one
showFeedbackBooleanShow the correct/incorrect feedback message after submission
eliminateWrongOptionsBooleanMultiple-choice only: previously-wrong options stay marked and click-disabled across attempts

CSS custom properties reference

These CSS variables are defined on the player's :root element. You can override any of them in Custom CSS.

Color scale

The slate color scale is the backbone of the player's visual hierarchy. In light themes, --slate-50 is the lightest and --slate-900 is the darkest. To create a dark theme, you invert this scale so that --slate-50 becomes dark and --slate-900 becomes light.

VariableDefault (Light)Used for
--slate-50#F8FAFCPage backgrounds, lightest surfaces
--slate-100#F1F5F9Card backgrounds, subtle surfaces
--slate-200#E2E8F0Borders, dividers, secondary backgrounds
--slate-300#CBD5E1Heavier borders, disabled states
--slate-400#94A3B8Placeholder text, icons
--slate-500#64748BSecondary text, captions
--slate-600#475569Body text (in dark themes)
--slate-700#334155Primary body text
--slate-800#1E293BEmphasized text, headings
--slate-900#0F172AStrongest text, titles

Semantic colors

VariableDefaultDescription
--primary-color#000000Primary brand color (set by primaryColor setting)
--primary-foregroundAuto-derivedText colour drawn on top of --primary-color (buttons, active nav). Set at runtime from primaryForegroundColor, or computed from primaryColor luminance when that setting is empty
--accent-color#000000Alias for primary color (backwards compatibility)
--accent-hover#333333Hover/active state (auto-derived or set by hoverColor)
--accent-lightrgba(0, 0, 0, 0.1)Light tint of primary (~15% opacity)
--outline-color#E2E8F0Button/element border color
--outline-thickness1pxButton/element border width
--success#10B981Success states (correct answers, completion)
--success-lightrgba(16,185,129,0.1)Success background tint
--error#EF4444Error states (incorrect answers)
--error-lightrgba(239,68,68,0.1)Error background tint

Typography

VariableDefaultDescription
--font-family'Inter', system-ui, sans-serifBase font stack
--font-family-headingvar(--font-family)Heading font
--font-family-bodyvar(--font-family)Body font
--font-weight-heading700Heading weight
--font-weight-body400Body weight
--text-xs to --text-4xl0.75rem to 2.25remType scale (xs, sm, base, lg, xl, 2xl, 3xl, 4xl)
--leading-tight1.25Tight line height
--leading-normal1.5Normal line height
--leading-relaxed1.625Relaxed line height

Spacing

VariableDefaultDescription
--spacing-base16pxBase spacing unit (set by spacing setting)
--space-10.25rem4px
--space-20.5rem8px
--space-30.75rem12px
--space-41rem16px
--space-61.5rem24px
--space-82rem32px
--space-123rem48px
--space-164rem64px

Border radius

VariableDefaultDescription
--radius-base8pxBase radius (set by borderRadius setting)
--radius-sm0.25remSmall radius
--radius-md0.375remMedium radius
--radius-lg0.5remLarge radius
--radius-xl0.75remExtra large radius
--radius-2xl1rem2x large radius
--radius-full9999pxFull/pill radius

Shadows

VariableDefault
--shadow-sm0 1px 2px 0 rgba(0,0,0,0.05)
--shadow-md0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)
--shadow-lg0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)
--shadow-xl0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1)

Transitions

VariableDefault
--transition-fast150ms ease
--transition-base200ms ease
--transition-slow300ms ease
--transition-slower500ms ease
--ease-outcubic-bezier(0, 0, 0.2, 1)
--ease-bouncecubic-bezier(0.34, 1.56, 0.64, 1)

Layout

VariableDefaultDescription
--sidebar-width280pxNavigation sidebar width
--header-height72pxPlayer header height
--footer-height64pxPlayer footer height
--content-max-width720pxMaximum content width

Built-in fonts

Slate includes 30 Google Fonts organized by category. Use the Font ID when setting headingFont or bodyFont in your theme.

Sans-serif (clean, modern)

Font IDNameWeights
interInter400, 500, 600, 700
open-sansOpen Sans400, 500, 600, 700
robotoRoboto400, 500, 700
latoLato400, 700
poppinsPoppins400, 500, 600, 700
nunitoNunito400, 600, 700
work-sansWork Sans400, 500, 600, 700
dm-sansDM Sans400, 500, 700
source-sans-3Source Sans 3400, 500, 600, 700
noto-sansNoto Sans400, 500, 600, 700
mulishMulish400, 500, 600, 700
rubikRubik400, 500, 600, 700

Serif (traditional, elegant)

Font IDNameWeights
playfair-displayPlayfair Display400, 500, 600, 700
merriweatherMerriweather400, 700
loraLora400, 500, 600, 700
source-serif-proSource Serif Pro400, 600, 700
crimson-proCrimson Pro400, 500, 600, 700
libre-baskervilleLibre Baskerville400, 700
bitterBitter400, 500, 600, 700

Display (headlines, impact)

Font IDNameWeights
montserratMontserrat400, 500, 600, 700, 800
ralewayRaleway400, 500, 600, 700
oswaldOswald400, 500, 600, 700
bebas-neueBebas Neue400
archivoArchivo400, 500, 600, 700
soraSora400, 500, 600, 700

Handwriting (friendly, personal)

Font IDNameWeights
caveatCaveat400, 700
dancing-scriptDancing Script400, 700
pacificoPacifico400

Monospace (code, technical)

Font IDNameWeights
fira-codeFira Code400, 500, 700
jetbrains-monoJetBrains Mono400, 500, 700
Custom fonts: Standard plan creators can upload their own fonts (WOFF2, WOFF, TTF, OTF). Custom font IDs use the custom- prefix (e.g., custom-acme-sans). See the Custom Fonts section in the builder's Font settings.

Custom CSS deep dive

The Custom CSS editor is where you unlock the full creative potential of Slate themes. Access it from Theme > Advanced in the builder.

How it works

Any CSS you write is injected into the player as a <style> tag. It loads after the default styles, so your rules override the defaults. You have access to all CSS custom properties and can target any player element by its class name or ID.

Security constraints

For security, the following CSS patterns are automatically blocked:

  • @import : no external stylesheet loading
  • javascript: in url() : no script injection
  • expression() : no IE expression evaluation
  • behavior: : no IE behavior injection
  • </style> : no tag breakout

Everything else is fair game.

Player structure & key selectors

Here's a map of the player's DOM structure with the CSS selectors you'll use most often.

Shell & layout

Shell selectors
body { }                        /* Page background (outside the player) */
#slate-player { }               /* The entire player container */
#player-content { }             /* Main content area */

Header

Header selectors
#player-header { }              /* Top bar with title and progress */
#course-title { }               /* Course title text */
#progress-bar { }               /* Progress bar track */
#progress-fill { }              /* Progress bar filled portion */
#progress-text { }              /* "75% Complete" text */

Navigation sidebar

Navigation selectors
#player-nav { }                 /* Sidebar container */
.nav-header { }                 /* Sidebar header area */
.nav-header-title { }           /* Course name in sidebar */
.nav-header-subtitle { }        /* Section subtitle */
.nav-section-title { }          /* Section headings */
.nav-lesson { }                 /* Individual lesson links */
.nav-lesson:hover { }           /* Lesson hover state */
.nav-lesson.active { }          /* Currently active lesson */
.nav-lesson.viewed { }          /* Previously completed lessons */
.nav-toggle { }                 /* Sidebar toggle button */
.nav-close { }                  /* Sidebar close button (mobile) */
.nav-overlay { }                /* Mobile backdrop overlay */

Footer

Footer selectors
#player-footer { }              /* Bottom navigation bar */
#btn-prev { }                   /* Previous button */
#btn-next { }                   /* Next button */
#btn-prev:disabled { }          /* Disabled previous */
#btn-next:disabled { }          /* Disabled next */

Text content

Text selectors
.block-text { }                 /* Text block container */
.block-text h1 { }              /* Heading 1 */
.block-text h2 { }              /* Heading 2 */
.block-text h3 { }              /* Heading 3 */
.block-text h4 { }              /* Heading 4 */
.block-text p { }               /* Paragraphs */
.block-text strong { }          /* Bold text */
.block-text a { }               /* Links */
.block-text a:hover { }         /* Link hover */
.block-text ul, .block-text ol { } /* Lists */
.block-text code { }            /* Inline code */
.block-text blockquote { }      /* Blockquotes */

Media blocks

Media selectors
.block-image figure { }            /* Image wrapper */
.block-image img { }               /* Image element */
.block-image figcaption { }        /* Image caption */
.block-video .video-wrapper { }    /* Video container — selector is scoped under .block-video for specificity */
.block-video .video-caption { }    /* Video caption */
.audio-block { }                   /* Audio block figure (the audio block's class is .audio-block, not .block-audio) */
.audio-block audio { }             /* Audio element */
.audio-block figcaption { }        /* Audio caption */
.block-iframe .iframe-wrapper { }  /* Embed container */

Interactive blocks

Interactive block selectors
/* Accordion */
.block-accordion { }            /* Accordion container */
.accordion-item { }             /* Individual accordion item */
.accordion-trigger { }          /* Clickable header */
.accordion-trigger:hover { }    /* Header hover */
.accordion-icon { }             /* Expand/collapse icon */
.accordion-content { }          /* Expanded content area */

/* Tabs */
.block-tabs { }                 /* Tabs container */
.tabs-header-wrapper { }        /* Tab bar */
.tab-button { }                 /* Individual tab */
.tab-button:hover { }           /* Tab hover */
.tab-button.active { }          /* Active tab */
.tab-panel { }                  /* Tab content area */

/* Cards */
.card { }                       /* Card container */
.card-title { }                 /* Card title */
.card-subtitle { }              /* Card subtitle */
.card-content { }               /* Card body */
.card-style-default { }         /* Default card variant */
.card-style-outlined { }        /* Outlined card */
.card-style-elevated { }        /* Elevated (shadow) card */
.card-style-filled { }          /* Filled background card */

/* Flip Cards */
.flip-card-face { }             /* Card face (front & back) */
.flip-card-body { }             /* Card face content */
.flip-card-hint { }             /* "Click to flip" hint */

/* Card Carousel */
.carousel-card { }              /* Carousel card */
.carousel-card-body { }         /* Carousel card content */
.carousel-nav { }               /* Prev/next arrows */
.carousel-dot { }               /* Pagination dot */
.carousel-dot.active { }        /* Active pagination dot */

Buttons

Button selectors
.button-primary, .slate-button.button-primary { }
.button-primary:hover, .slate-button.button-primary:hover { }
.button-secondary, .slate-button.button-secondary { }
.button-secondary:hover, .slate-button.button-secondary:hover { }
.button-outline, .slate-button.button-outline { }
.button-outline:hover, .slate-button.button-outline:hover { }

Dividers

Divider selectors
.divider-line { }               /* Line divider */
.divider-dots::before { }       /* Dotted divider */

Tables

Table selectors
.block-table .table-wrapper { }
.slate-table { }                /* Table element */
.slate-table th { }             /* Table header cells */
.slate-table td { }             /* Table body cells */
.slate-table thead th { }       /* Top header row */
.slate-table th[scope="row"] { } /* Row headers */
.slate-table.table-border-all th,
.slate-table.table-border-all td { }       /* All borders variant */
.slate-table.table-border-horizontal th,
.slate-table.table-border-horizontal td { } /* Horizontal borders */
.slate-table.table-stripe-even tbody tr:nth-child(even) { }
.slate-table.table-stripe-odd tbody tr:nth-child(odd) { }
.block-table figcaption { }     /* Table caption */

Hotspots

Hotspot selectors
.hotspot-popover { }            /* Popover container */
.hotspot-popover-header { }     /* Popover header */
.hotspot-popover-body a { }     /* Links inside popovers */
.hotspot-marker.active { }      /* Active hotspot marker */

Knowledge checks

Knowledge check selectors
.block-knowledge-check { }      /* Quiz container */
.kc-question { }                /* Question text */
.kc-hint { }                    /* Hint text */
.kc-option { }                  /* Answer option */
.kc-option::before { }          /* Radio/checkbox indicator */
.kc-option:hover { }            /* Option hover */
.kc-option.selected { }         /* Selected option */
.kc-option.correct { }          /* Correct answer (after submit) */
.kc-option.incorrect { }        /* Incorrect answer (after submit) */
.kc-submit { }                  /* Submit button */
.kc-feedback.correct { }        /* Correct feedback message */
.kc-feedback.incorrect { }      /* Incorrect feedback message */

Assessment (scored quizzes)

Assessment selectors
.assessment-intro { }           /* Assessment intro card */
.assessment-intro-icon { }      /* Icon container */
.assessment-intro-title { }     /* Assessment title */
.assessment-detail { }          /* Stats (questions, passing score) */
.assessment-start-btn { }       /* Start button */
.assessment-header { }          /* In-progress header */
.assessment-question-wrapper { } /* Question card */
.assessment-submit-btn { }      /* Submit assessment */
.assessment-results { }         /* Results card */
.assessment-results.passed { }  /* Passed state */
.assessment-results.failed { }  /* Failed state */
.assessment-results.locked { }  /* No retries remaining */
.assessment-score-circle { }    /* Score display */
.assessment-retry-btn { }       /* Retry button */

Scrollbar & focus

Scrollbar & focus selectors
::-webkit-scrollbar { }         /* Scrollbar (WebKit) */
::-webkit-scrollbar-track { }   /* Scrollbar track */
::-webkit-scrollbar-thumb { }   /* Scrollbar handle */
:focus-visible { }              /* Keyboard focus outline */
.skip-link { }                  /* Skip navigation link */

The dark theme technique

Creating a dark theme in Slate is straightforward: invert the slate color scale so that low numbers are dark and high numbers are light. This works because the entire player uses the --slate-* variables semantically, so flipping the scale flips the entire UI.

Dark theme: inverted color scale
:root {
  /* Inverted color scale: dark backgrounds, light text */
  --slate-50: #18181b;   /* Was near-white, now near-black */
  --slate-100: #1f1f23;
  --slate-200: #27272a;
  --slate-300: #3f3f46;
  --slate-400: #71717a;
  --slate-500: #a1a1aa;
  --slate-600: #d4d4d8;
  --slate-700: #e4e4e7;
  --slate-800: #f4f4f5;
  --slate-900: #fafafa;  /* Was near-black, now near-white */
}

body {
  background: #09090b;   /* Darkest background for the page */
}

That single override transforms the entire player into dark mode. From there, you can customize accent colors, gradients, and effects to taste.

Theme presets

Once you've dialled in a look you'll want again, save it as a theme preset. A preset bundles every Theme setting plus your Custom CSS into a named entry on your account. Apply it to any course with one click, set it as the default for new courses, or share it across your team.

What a preset includes

  • Every Theme setting in this guide (colours, typography, layout, navigation, knowledge-check defaults)
  • Your Custom CSS, exactly as written
  • Your uploaded logo reference

How to use them

  • Save: on the Theme page, click Save as preset and give it a name
  • Apply: open the preset picker on any course and pick one
  • Update: overwrite a saved preset with your latest changes
  • Import as preset: drop in a theme .json file and save it straight to your library
  • Team default: on Team plans, one preset can be marked as the default so new courses inherit it automatically
Preset limits scale with your plan. Free creators can save one preset; paid plans allow more, with Team workspaces giving the most headroom for shared brand libraries. See pricing for the current limits.

Example themes

Midnight Aurora

A dark theme with vibrant purple accents, gradient effects, and glowing elements. This is one of Slate's built-in themes.

Midnight Aurora: settings JSON
{
  "primaryColor": "#8B5CF6",
  "hoverColor": "#7C3AED",
  "outlineColor": "rgba(139, 92, 246, 0.3)",
  "outlineThickness": 1,
  "borderRadius": 12,
  "spacing": 16
}

Classic Dark

A cleaner, more professional dark theme using blue as the accent color. Uses var() references extensively, making it easy to adapt by changing the accent color variables.

Classic Dark: settings JSON
{
  "primaryColor": "#3B82F6",
  "hoverColor": "#2563EB",
  "outlineColor": "rgba(63, 63, 70, 0.8)",
  "outlineThickness": 1,
  "borderRadius": 8,
  "spacing": 16
}

Theme export/import format

Themes can be exported as JSON files and shared with others. This is the format Slate uses:

Theme export JSON schema
{
  "_slateThemeExport": true,
  "exportVersion": "1.0",
  "exportedAt": "2026-05-27T12:00:00.000Z",
  "courseName": "My Course",
  "theme": {
    "primaryColor": "#8B5CF6",
    "primaryForegroundColor": "",
    "hoverColor": "#7C3AED",
    "outlineColor": "rgba(139, 92, 246, 0.3)",
    "outlineThickness": 1,
    "borderRadius": 12,
    "spacing": 16,
    "headingFont": "playfair-display",
    "bodyFont": "inter",
    "headingFontWeight": 700,
    "bodyFontWeight": 400,
    "customCss": "/* Your custom CSS here */",
    "logoUrl": "",
    "showScrollIndicator": false,
    "enableSearch": true,
    "navigationLayout": "classic",
    "contentLayout": "standard",
    "lockedNavigation": false,
    "assessmentAutoscroll": true,
    "showExitCourse": false,
    "knowledgeChecks": {
      "maxAttempts": 0,
      "revealCorrectAnswer": true,
      "revealAnswersPerAttempt": false,
      "showFeedback": true,
      "eliminateWrongOptions": false
    }
  }
}

Required fields

_slateThemeExport (must be true), exportVersion, exportedAt, and the theme object with at minimum primaryColor, outlineColor, outlineThickness, borderRadius, spacing, headingFont, bodyFont, headingFontWeight, and bodyFontWeight. Everything else is optional and falls back to a sensible default when omitted.

Validation rules

  • Colors must be valid hex (#XXXXXX) or rgba() format
  • hoverColor and primaryForegroundColor can be an empty string (auto-derived) or a valid color
  • outlineThickness: integer 0-4
  • borderRadius: 0-24
  • spacing: 0-32
  • Font weights: 100-900 in steps of 100
  • Custom CSS: max 50,000 characters
  • Font IDs must match a built-in font or use the custom- prefix
  • navigationLayout: classic or vertical
  • contentLayout: standard or wide
  • knowledgeChecks, when present, must include all five fields (maxAttempts, revealCorrectAnswer, revealAnswersPerAttempt, showFeedback, eliminateWrongOptions). Omit the field entirely to inherit Slate's built-ins

How to use

  1. Export: Go to Theme page, click the export button to download a .json file
  2. Import: Click import and select a theme JSON file
  3. Share: Send the JSON file to colleagues or post it online

Step-by-step: building a custom dark theme

Let's build a dark theme from scratch with a teal accent color.

1Set the theme settings

In the Slate builder, go to the Theme page and set:

  • Primary Color: #14B8A6 (teal)
  • Hover Color: leave empty (auto-derived)
  • Outline Color: rgba(20, 184, 166, 0.3)
  • Outline Thickness: 1
  • Border Radius: 10
  • Spacing: 16
2Write the Custom CSS: color scale

Open the Custom CSS editor and start with the inverted color scale:

Step 2: Inverted color scale + accent colors
/* Teal Dark Theme */
:root {
  /* Inverted slate scale for dark mode */
  --slate-50: #0f1419;
  --slate-100: #151c22;
  --slate-200: #1c252d;
  --slate-300: #2a3541;
  --slate-400: #5c6b7a;
  --slate-500: #8899a8;
  --slate-600: #b0c0cf;
  --slate-700: #d0dbe5;
  --slate-800: #e8eff4;
  --slate-900: #f5f8fa;

  /* Teal accent colors */
  --accent-color: #14b8a6;
  --accent-hover: #0d9488;
  --accent-light: rgba(20, 184, 166, 0.15);
  --primary-color: #14b8a6;

  /* Status colors */
  --success: #22c55e;
  --success-light: rgba(34, 197, 94, 0.15);
  --error: #f43f5e;
  --error-light: rgba(244, 63, 94, 0.15);

  --outline-color: rgba(20, 184, 166, 0.3);
  --outline-thickness: 1px;
}
3Style the shell
Step 3: Body & player background
body {
  background: #0a0f13;
  color: var(--slate-700);
}

#slate-player {
  background: var(--slate-50);
}
4Style header and footer
Step 4: Header & footer
#player-header {
  background: rgba(15, 20, 25, 0.95);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid rgba(20, 184, 166, 0.15);
}

#course-title { color: var(--slate-900); }
#progress-bar { background: rgba(20, 184, 166, 0.1); border: 1px solid rgba(20, 184, 166, 0.2); }
#progress-fill { background: var(--accent-color); }

#player-footer {
  background: rgba(15, 20, 25, 0.95);
  backdrop-filter: blur(12px);
  border-top: 1px solid rgba(20, 184, 166, 0.15);
}

#btn-next { background: var(--accent-color); color: #fff; }
#btn-next:hover:not(:disabled) { background: var(--accent-hover); }
#btn-prev { background: var(--slate-200); color: var(--slate-800); border: 1px solid var(--slate-300); }
5Style the navigation
Step 5: Navigation sidebar
#player-nav {
  background: rgba(10, 15, 19, 0.98);
  border-right: 1px solid rgba(20, 184, 166, 0.1);
}

.nav-header { background: var(--slate-50); border-bottom: 1px solid rgba(20, 184, 166, 0.1); }
.nav-header-title { color: var(--slate-900); }
.nav-section-title { color: #2dd4bf; }

.nav-lesson { color: var(--slate-600); border: 1px solid transparent; }
.nav-lesson:hover { background: rgba(20, 184, 166, 0.08); color: var(--slate-800); }
.nav-lesson.active { background: var(--accent-color); color: #fff; }
6Style content blocks
Step 6: Content block overrides
.block-text a { color: #2dd4bf; }
.block-text a:hover { color: #5eead4; }
.block-text code { background: var(--slate-200); color: #2dd4bf; }
.block-text blockquote { border-left: 3px solid var(--accent-color); background: var(--slate-100); }

.button-primary, .slate-button.button-primary { background: var(--accent-color); color: #fff; }
.button-primary:hover, .slate-button.button-primary:hover { background: var(--accent-hover); }
7Export and share

Once you're happy with the result, export your theme as JSON from the Theme page. You can share the file with your team, apply it to other courses, or save it as your default theme for new courses.

Tips and best practices

  • Start with a built-in template that's closest to your goal, then customize from there
  • Use the Theme Preview (desktop) to see changes in real-time as you edit
  • Save your theme as default so all new courses start with your brand
  • Export before experimenting so you can always roll back
  • Test with all block types: create a test course with one of every block type to verify your theme covers everything
  • Dark themes need extra attention on success/error colors. Make sure green and red are visible on dark backgrounds
  • Font pairing: try a serif heading font with a sans-serif body font for elegant contrast (e.g., Playfair Display + Inter, or Merriweather + Work Sans)
  • Use var() references instead of hardcoding colors. This makes themes easier to modify later
  • Gradients on #progress-fill, #course-title, .button-primary, .nav-lesson.active create visual interest
  • Glassmorphism via backdrop-filter: blur(12px) + semi-transparent rgba() backgrounds on header and footer
  • Glow effects via box-shadow: 0 0 Npx rgba(accent, 0.3-0.5) on active elements
  • Always style both .button-primary and .slate-button.button-primary for full coverage
  • Scrollbar styling makes a big difference in dark themes. Match it to your color scheme