Custom CSS, custom HTML head, and raw HTML sections are escape hatches for Uber subscribers. They are powerful and they are easy to break your page with. Read this first.
Every leetpage exposes the active theme as CSS custom properties on :root. Reading from these instead of hardcoding hex values is what makes overrides survive a theme switch.
| variable | role |
|---|---|
| --bg | page background |
| --bg2 | card / panel background |
| --accent | primary accent (green by default, varies by theme) |
| --accent-glow | rgba shadow color for accent glows |
| --cyan | secondary accent / links |
| --yellow | tertiary accent (achievements, warnings) |
| --text | primary text color |
| --text2 | muted body text |
| --dim | captions / very muted labels |
| --border | card + section borders |
Use var(--accent) wherever you would have written a green hex. Switch from Hacker theme to Cyberpunk and the same rule now renders pink. Hardcode #00ff88 and your page looks broken on every other theme.
The Customize tab in the dashboard has a Custom CSS textarea. Anything you type there is injected into a <style> block after the theme stylesheet, so your rules win cascade ties. Cap is 50 KB.
Target specific selectors. Reach for var(--accent) instead of hex.
/* widen the wrapper for a long bio */
.wrapper { max-width: 920px; }
/* yellow accent on a single section header */
.section[data-section="achievements"] .section-title {
color: var(--yellow);
letter-spacing: 5px;
}
/* glow your custom hero logo using the active theme color */
.hero-logo {
filter: drop-shadow(0 0 24px var(--accent-glow));
}
Hardcoded colors break on theme switch. Global selectors break the rest of the page. Both are how the green-on-everything bug got shipped.
/* bad - locked to one theme */
.hero-logo { filter: drop-shadow(0 0 20px #00ff88); }
/* bad - hostile to every other section */
* { color: lime !important; }
/* bad - fights the theme cascade instead of using it */
body { background: black !important; color: #0f0 !important; }
var(--...), ask whether it should. If it does not need !important, do not add it.The Custom HTML head textarea injects raw HTML into <head>. Cap is 4 KB. Use it for:
<link rel="stylesheet" href="https://fonts.googleapis.com/..."><meta property="og:image" content="https://...">Server-side sanitizer drops anything outside this allowlist:
<meta> (charset overrides + meta-refresh are stripped)<link rel="icon|stylesheet|preload|preconnect|dns-prefetch|manifest|alternate|canonical"> with an https href<title> (contents are HTML-escaped)<noscript> wrapper (contents are HTML-escaped)<style> (re-scrubbed through the CSS sanitizer)<script src="https://..."> from the allowlist only - inline JS is droppedAdding a section of kind raw_html from the Sections tab gives you a free-form HTML block in the body. Cap is 64 KB. Common content tags are allowed (p, div, a, img, span, ul, table, headings, etc.).
This is the escape hatch for embedding a one-off badge wall or a custom layout the built-in section kinds do not cover. Same scrubbing as the head field: no inline JS, no on*= handlers, no javascript: or data: URLs, and iframes are limited to the YouTube allowlist.
<script> with inline JS - dropped from every field<script src="..."> outside this host allowlist: plausible.io, cdn.plausible.io, cdn.usefathom.com, static.cloudflareinsights.com, www.googletagmanager.com, cdn.jsdelivr.net, unpkg.comon*= event handlers (onclick, onload, etc.) - stripped on savejavascript:, data:, vbscript:, file:, blob: URLs in href / src attrs@import url(...) in CSS - blocks fetching an attacker stylesheeturl(javascript:...) / url(data:...) in CSS propertiesexpression(...), -moz-binding:, behavior: in CSS<iframe> outside the YouTube host allowlist<meta http-equiv="refresh">The Content-Security-Policy header enforces these in the browser even if a bug slipped one past the server. Violations are logged to our audit table.
Questions or a pattern that should be allowlisted? Open an issue on the repo or ping support.