Skip to Content
GuidesDark Mode Support

Dark Mode Support

Every Gymlium template must support both dark and light colour modes. The platform handles mode switching — your job is to define the visual styles for each mode.

How theme switching works

The Gymlium platform sets a data-theme attribute on the <html> element:

<!-- Dark mode --> <html data-theme="dark"> <!-- Light mode --> <html data-theme="light">

Your template defines colour variables for each mode in template.css, and your component styles reference those variables.

Setting the default mode

Trainers choose their site’s default mode when they sign up and can change it any time in Dashboard → Appearance. Your template does not set it, so it must look right in every mode:

ModeBehaviour
"dark"Site loads in dark mode. Visitor can toggle to light.
"light"Site loads in light mode. Visitor can toggle to dark.
"auto"Follows the visitor’s system preference (prefers-color-scheme).

Defining theme variables

In template.css, define your colour palette for both modes:

[data-theme="dark"] { --bg-primary: #0f172a; --bg-secondary: #1e293b; --bg-card: #1e293b; --text-primary: #f8fafc; --text-secondary: #94a3b8; --border-color: #334155; --shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } [data-theme="light"] { --bg-primary: #ffffff; --bg-secondary: #f8fafc; --bg-card: #ffffff; --text-primary: #0f172a; --text-secondary: #64748b; --border-color: #e2e8f0; --shadow: 0 4px 12px rgba(0, 0, 0, 0.08); }

Then reference these variables in your component styles:

/* Card.module.css */ .card { background: var(--bg-card); color: var(--text-primary); border: 1px solid var(--border-color); box-shadow: var(--shadow); border-radius: var(--border-radius); padding: 1.5rem; }

Mode-specific styles in CSS Modules

When you need styles that differ beyond colour variables (e.g. different background images, opacity values, or decorative elements), use the :global([data-theme]) selector within your CSS Module:

.hero { background-image: url('/hero-dark.jpg'); background-size: cover; } :global([data-theme="light"]) .hero { background-image: url('/hero-light.jpg'); } .badge { opacity: 0.9; } :global([data-theme="light"]) .badge { opacity: 1; }

Light and dark photos

Every image a trainer manages has an optional dark version. Gymlium stores it next to the light one as <field>_dark (for example image_url and image_url_dark) and shows it when the site is in dark mode. When a trainer leaves the dark version empty, the light photo is used in both modes, so your template must still look right with the same photo in both.

The sample photos that seed a new site come in pairs: the dark one is the same photo graded darker, so switching modes never swaps the person or the scene. Design your photo spots for both versions.

Declare the photo spots your template shows in the manifest’s imageSlots so signup can preview them at the right shape in both modes.

Using Tailwind’s dark mode

If you use Tailwind utilities, the dark: prefix is wired to the platform’s theme system:

<div className="bg-white dark:bg-slate-900 text-slate-900 dark:text-white"> <p className="text-slate-600 dark:text-slate-400"> This adapts to the active theme. </p> </div>

Testing both modes

Always test your template in both dark and light modes before submitting. Common issues caught during review:

  • Text invisible against same-coloured background in one mode
  • Borders or dividers invisible in one mode
  • Images with transparent backgrounds that look broken in one mode
  • Hardcoded colours that don’t adapt

The local preview server includes a theme toggle in the toolbar. Use it to switch between modes while developing.

Checklist

  • All text is readable against its background in both modes
  • Borders and dividers are visible in both modes
  • Images and icons have appropriate contrast in both modes
  • Photo spots look right with a dark version and with the light photo shown in both modes
  • Form inputs have visible borders and focus indicators in both modes
  • Hover and focus states are visible in both modes
  • No hardcoded colour values — everything uses CSS custom properties
Last updated on