Administrator comparing a light and a dark forum layout on a single widescreen monitor

XenForo 2.3 already has a theme switcher. It is not an add-on. It is style variations: one style, two palettes, a control that flips between them. Administrators who still install a third-party “dark mode switcher” are solving a 2.2 problem on a 2.3 board.

This is not a redesign manual. The later “customize a theme without breaking updates” article owns deep extra.less, template forks, and vendor parents. This one is the switch: how official Color palette Light/Dark works, how 2.3 community ACP practice enables variations on a style, why the style chooser is a different gadget, and how you stop hard-coded hex from breaking the dark half.

The 2.3 upgrade article already told you variations exist and that you should test both after upgrade. The nodes article already told you a variant-aware property can differ between light and dark. Read those for the surrounding work. Stay here for the switch itself.

Two gadgets, two jobs

Members say “I want dark mode.” The ACP has two ways to give them a different look. Mixing the names is how you ship two dark styles and a cookie hack.

Gadget What it is What the member does When you use it
Style chooser More than one style (Default, a child, a paid parent) Picks a style in preferences, or you force the default Brand A vs brand B, or a staff-only style. Official: Default style plus optional user selection
Light / dark switch Two variations inside one style Flips default ↔ alternate without changing style ID The 2.3 dark mode people actually mean

Official Appearance options: you set which installed style is the Default style for visitors who have not chosen, and you set a default style for HTML email in the same place. The style called “Default style” is just the name of the one that shipped. You can rename it. Email can use a different style from the public site — useful when the public variation is dark and you still want a readable message.

A member who picks Style B in the chooser is not flipping a variation. They are loading a different inheritance tree: different templates, different properties, different outdated-template queue. Two styles that are “the same except dark” are two merge jobs every upgrade. Variations were invented so you stop doing that.

Do not hide the chooser and install a switcher add-on. Enable variations on the style you already serve. If you need a second brand, then you need a second style. If you need night-time contrast, you need a second variation.

Official colour machinery (still the foundation)

Official style-properties page (docs.xenforo.com/manual/appearance/style-properties), the part that existed before 2.3 had a name for variations:

Appearance → Styles → [your style] → Style properties.

Properties inherit from the parent unless you customize them here. They come in single-value types (colours, text, numbers with units, switches, multiple-choice) and CSS-type groups (text / background / border / padding / extra CSS).

Color palette is the first group that matters. It defines the chips the rest of the system references. Changing a palette colour replaces every use of that chip. Official: there is a switch to set the style type as Light or Dark. That switch does not mean “this style is the dark theme.” It tells intensify / mix / diminish which way to push a colour. If the type is Light and the system intensifies, it darkens. If the type is Dark, intensify lightens. Get this backwards and every hover state goes the wrong way.

Basic colors assign those chips to roles: text, content background, and friends. Official reference syntax in properties and Less:

@xf-paletteNeutral3
@xf-textColor

Prefix @xf- plus the property ID. In the Default style, Text color (textColor) uses @xf-paletteNeutral3 — a very dark grey. Change the chip, not fifty templates.

CSS-type properties (Header and navigation, Header/logo row, and the rest) are where you stop painting individual pages. Official walkthrough: open Header/logo row, point text at @xf-paletteColor2 and the background at @xf-paletteColor5, save, load a public page. That is a rebrand. It is also how you keep a dark variation honest: the roles stay; the chips change per variation.

Templates that are .less / .css read the same @xf-stylePropertyName tokens. Hard-coded #1a1a1a in extra.less does not. The mistakes article already has the ticket titled “dark mode broke extra.less.”

2.3: enable variations on the style

The short official styles page describes inheritance and the Light/Dark type switch. It does not, in that page, walk the 2.3 variations UI. Community 2.3 ACP practice does, and it matches what the upgrade article already shipped:

  1. Appearance → Styles.
  2. Click the style you serve (usually a child of Default or of a vendor parent — not Master).
  3. Enable variations on that style.
  4. You now have a default variation and an alternate variation. Certain style properties become variation-aware: they can hold two values.
  5. Edit Color palette (and Basic colors as needed) for each variation. Set the style type Light on the light half and Dark on the dark half so intensify/mix agree with what the eye sees.
  6. Save. Load the public site. Use the variation control. Do not skip the other half.

Official 2.3 materials (as already summarised in the upgrade article): variations hook the browser prefers-color-scheme media query, and guests get a footer gadget that flips the variation without a reload. Community threads discuss aligning the first paint to the OS when variations are enabled. The public styles page does not document a separate “follow OS” checkbox in the prose cited here. If your ACP shows an extra control, use it; do not invent one in a tutorial. If it does not, the media query plus the footer gadget is the switcher.

Work in a child. Official inheritance: a style with no parent inherits from the hidden Master style. A child of Default inherits Default, which inherits Master. Customizations in the child override the parent. When 2.3.x ships new property defaults, you want one outdated child, not a fork of Default you can no longer merge.

Create that child the official way, before you touch a palette:

  1. Appearance → Styles → Add style.
  2. Title it something you will still understand in a year (Board, not dark2).
  3. Set the parent to Default style, or to the vendor 2.3 parent you actually serve.
  4. Leave user-selectable off until you mean guests to pick it.
  5. Make it the Default style under Appearance options only after both variations are readable.

Vendor styles: import the 2.3 package as a new parent, keep your child, merge outdated templates. A 2.2 “dark style” that forked PAGE_CONTAINER is not a variation. It is a merge queue. Enable variations on the 2.3 parent (or its child) instead of running two paid products.

Variation-aware versus one value

Community 2.3 ACP practice: once variations are on, some properties show two values. Official public style-properties prose does not publish a labelled list of which IDs are variation-aware. Operational rule:

Kind of property What to expect What you do
Color palette chips Two sets once variations are enabled Recolour both. Set Light / Dark type on each set
Basic colors roles (textColor, content background) Usually two sets — these are the dark mode Point each at a palette chip, not a hex
CSS-type groups (header row, buttons, node list) Mixed. Some fields vary, some do not Open the group, look. If there is only one value, both variations share it
extra.less Never variation-aware by itself Tokens and xf-intensify / xf-diminish, or you write both colours

If a border, overlay, or editor chrome is unreadable on only one half, that control was a single value. Either make it a token that works on both surfaces, or find the variation-aware property the vendor already exposed. Do not fork the template to invent a second border.

Inheritance, in one picture

Official example, still the one that matters when you add a dark half:

  • (1) Default style
    • (2) My style (your child)

textColor starts as @xf-paletteNeutral3 in Master, inherited by Default, inherited by My style. Change Default to blue and My style goes blue unless My style customized textColor. Change My style to red and it stops inheriting blue.

Variations sit inside My style. They are not a third row in that tree. Alternate is not a child style. If you create “My style Dark” as a sibling, you are back to two merge jobs. Do not.

Official inheritance summary already showed a tree of Custom header → Big footer / Small text → Red / Blue backgrounds. That pattern is for brand forks (a red event microsite). It is the wrong pattern for night mode. Night mode is two palettes on Custom header.

HTML email: official default-style-for-email is a style, not a variation. If dark-on-dark mail is unreadable, point email at a style whose default variation is light. Do not assume the member’s public flip applies to the message.

Worked mail check, because this is the ticket that arrives a week later:

  1. Appearance options → default style for HTML email → Default style (light), even if the public default is Board.
  2. Setup → Options → Email options still owns transport (PHP built-in mail, SMTP, Google OAuth). A dark brick is a style problem; a missing mail is transport. Do not confuse them.
  3. Trigger a conversation reply or a watched-thread mail to a throwaway you can open in a desktop client and on a phone.
  4. If the client forces a dark canvas on all mail, your light template can still invert. That is the client. You cannot fix Apple Mail with a XenForo variation. You can keep the HTML from shipping near-black text on a transparent background — that is the Basic colors job on the email style.

What the member actually clicks

Once variations are enabled on the active style:

  • A guest can flip the variation (community / 2.3 materials: footer gadget, no reload). Their choice is a cookie or equivalent client state — official public pages cited here do not name the cookie. Operational test: private window, flip, refresh, still flipped. Another private window, OS set the other way, see what first paint does.
  • A registered member gets the same flip plus, if you allow style selection, the chooser. Those must not fight. If Default style is the only selectable style, the chooser is noise — official Appearance options include a toggle to prevent selection. Use it when you have one public style.
  • Staff previewing as a guest still need a second window. Admin ACP is not the public variation. The mistakes habit stands: guest, throwaway Registered, staff, after every palette save.

PWA colours are a different switch. Official PWA setup: Meta theme color + Page background color; the default style drives the rest; PWA language is one installed language. A dark public variation with a light meta theme colour is a status-bar lie on a phone. Set those two colours to survive both variations or accept that install-to-home-screen will pick the default style’s values. The later push/PWA article owns the rest.

Build the dark half without painting yourself into a merge

Order of operations that survives the next 2.3.x:

  1. Child style. Parent is Default or the vendor 2.3 parent.
  2. Enable variations on the child (or on the parent if the vendor already did — then only override what you must).
  3. Color palette for the light variation. Style type Light. Change chips, not templates.
  4. Color palette for the alternate variation. Style type Dark. Recolour the same chips. Check contrast on text / background roles in Basic colors.
  5. Walk Header and navigation, Node / forum list, buttons, and overlays in both variations. Variation-aware properties can differ. A property that is not variation-aware stays one value — that is when a light-only border survives on a dark page.
  6. extra.less only for what properties cannot do. Prefer @xf-… tokens and official Less helpers xf-intensify / xf-diminish (variation-aware: they darken a light variation and lighten a dark one). Never #111 on a background both variations use.
  7. Node icons: 2.3 SVG sprites. The nodes extra.less pattern (hide the SVG, .m-faContent(@fa-var-…)) must be checked on both variations. A white icon on a white unread state is a variation bug, not a missing font.
  8. Appearance → Outdated templates after every core or vendor update. Merge. Do not paste the 2.2 PAGE_CONTAINER back to “restore dark mode.”

Official: customized templates are not overwritten on upgrade. That is why they go outdated. The merge tool highlights successful merges in blue and conflicts in yellow. Official: check immediately; important functionality may be missing if you leave them.

Widgets that inject HTML with inline colours will not flip. Page-node landing HTML is the same trap. Use classes and palette tokens, or accept that the campaign page is one theme.

OS, guests, and what not to invent

Community threads discuss matching the first paint to the operating system when variations are enabled. That is a real member expectation: their laptop is dark at 23:00; the forum should not flash white. 2.3 materials already mention prefers-color-scheme. What the short official styles page does not give you is a labelled “Always follow OS / Always light / Always dark” tri-state for members. Do not write a tutorial that names that tri-state as a core option unless you are looking at it in your ACP.

Honest operational pattern:

  • Enable variations.
  • Check a guest with OS light and OS dark.
  • Check that the footer (or header) gadget overrides OS for that browser.
  • If a registered member also has a selected style, remember you are now testing style × variation.

If that matrix is too wide, turn off user style selection and serve one style with two variations. That is the whole point of 2.3.

A third-party switcher that loads a second style via JS is how you get a flash of the default palette, a second CSS payload, and a support queue that cannot reproduce the bug because the tester’s OS disagrees. You do not need it.

Historical “dark theme” products

Older shop pages sold Roleplay Dark, Roleplay Light, and similar pairs as if night mode were a second SKU. It is not. A dark gaming look is this article: palette chips, style type Dark, variations enabled. The roleplay / gaming architecture article is the tree, the whitelist, and the fields. Do not buy a skin to get a #0b0f14 header.

If a vendor still ships two complete styles instead of one style with variations, you are buying two outdated-template lists. Prefer a 2.3 package that documents variations. If you already own a pair, pick one as parent, enable variations, copy the dark chips into alternate, and retire the sibling when the child is stable.

Worked ACP hour

You run Default plus a child named Board that only customizes the logo and extra.less.

  1. Appearance → Styles → Board. Enable variations.
  2. Light variation: leave palette close to Default. Style type Light. Confirm @xf-textColor still reads as near-black on a near-white content background.
  3. Alternate variation: set Neutral / color chips to a dark surface and light text. Style type Dark. Confirm intensify lightens a hover.
  4. Header/logo row: backgrounds via @xf-paletteColor…, not a hex you liked in a screenshot.
  5. Public site as guest. Flip. Refresh. Flip back.
  6. Register a throwaway. Flip. Open a conversation, a thread, the member card, the editor. Dark-on-dark editor chrome is the usual miss.
  7. Send yourself a test mail. If it is unreadable, set the HTML email default style to Default (light) and leave Board as the public default.
  8. Open extra.less. Search for #. Every hit is a suspect. Replace with a token or wrap in a variation-aware helper. Save. Flip again.
  9. Configuration audit: cookie path still sane, debug off, both variations readable on a phone-width window.

That is dark mode. The later customization article can talk about type scales and card radii. Not tonight.

A testing matrix you can print

After the hour, walk this table before you announce the flip. It is shorter than a second style.

Surface Light Dark What usually fails
Forum list / node icons Read + unread Read + unread 2.2 webfont CSS; light-only fill
Thread view, first post + reply Text on content bg Same Hex in extra.less; quote/block backgrounds
Editor (quick and full) Toolbar + textarea Same Chrome still Light-typed
Member profile + hover card Banners readable Same Banner CSS from a group, not a palette chip
Conversations List + pane Same Unread highlight identical to background
Notices / widgets HTML widgets Same Inline style="color:#…"
Login / lost password Form Same Often still Default style if you left a second style selectable
HTML email Opened in two clients n/a Email style was the public dark style

Three sessions on each row you care about: guest, throwaway Registered, staff. Staff-only ACP greys do not count. The configuration mistakes article already told you that if you only test as admin, you ship admin’s board.

What usually looks broken

Symptom Likely cause First move
Flip does nothing Variations not enabled on the active style Check which style Default style points at
Flip works, first paint always light OS / prefers-color-scheme not applied, or a second style forced Guest window; disable chooser; retest
Dark is black-on-black Style type still Light on the dark variation, or hex in extra.less Type switch + search extra.less for #
Only the header went dark You edited Header properties, not the palette Palette chips first; roles second
Node icons vanished in dark 2.2 webfont CSS or a light-only fill Nodes article SVG pattern; both variations
Email is a dark brick Email uses the public style’s dark default Separate HTML email style
Staff say “it looks fine” They never left the ACP or never flipped Three sessions, both variations
Upgrade restored “the old dark style” Outdated PAGE_CONTAINER or a second style still selectable Merge; disable the sibling style

What this is not

It is not permission to fork PAGE_CONTAINER for a hero that is dark after 18:00. Official 2.3 CSS is unbundled. A novel in global extra.less invalidates the global sheet. Page-only CSS belongs in <xf:css> on that page.

It is not a second language pack. Appearance → Languages is a different inheritance tree. RTL, date formats, and phrases do not flip with the palette.

It is not PWA installability. Icons 192 and 512, short title, HTTPS — PWA setup adjacent, later article owns push.

It is not “install Style B for mobile.” Responsive is already the core layout. A mobile-only style is a second chooser entry and a second bug.

Checklist

  • One public style. Variations enabled on that style (community 2.3 path: Appearance → Styles → click the style).
  • Color palette style type is Light on the light variation and Dark on the dark one.
  • Basic colors use @xf-… chips, not pasted hex, for text and content background.
  • User style selection is off unless you truly have two brands.
  • HTML email default style is readable on a white mail client.
  • extra.less has no raw #111 / #fff on shared surfaces.
  • Guest flip survives refresh. OS light and OS dark both produce a usable first paint, or you have documented that they do not.
  • Both variations checked on forum list, thread view, editor, member profile, conversations.
  • After the next upgrade: Outdated templates, both variations, Icon Usage Analyzer.
  • No third-party switcher is load-bearing.

Takeaways

  • 2.3 style variations are the theme switcher. You do not need a plugin to flip light and dark.
  • Official Color palette Light/Dark is the intensify/mix type, not a second style.
  • The style chooser picks a style ID. The variation control picks a palette inside that ID. Do not use two styles to fake night mode.
  • Child style, palette chips, @xf- tokens, xf-intensify / xf-diminish. Hex in extra.less is how dark mode “breaks.”
  • Test both variations after every upgrade. The later theme-customization article can go deeper. The switch is done when a guest can flip and still read the thread.

Turn the variations on. Recolour the chips. Delete the switcher add-on from the shopping list.