Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungKI half beim Finden: iOS 27 schließt mehr als 100 Sicherheitslücken(21.09.2026 um 06:00 Uhr)
Sichere ProgrammierungWhat Is Rowhammer? How Can Repeated Memory Access Flip Bits in RAM?(21.09.2026 um 07:12 Uhr)
Sichere Programmierungnpm publish Ignores .gitignore: The .npmignore Override Rule(21.09.2026 um 07:15 Uhr)
Sichere ProgrammierungAphelion Editor - A free node-based video / VFX editor(21.09.2026 um 07:21 Uhr)
Sichere ProgrammierungGovernance Attack Surface Review: OKX(21.09.2026 um 07:31 Uhr)
Sichere ProgrammierungJSM Portal Request Create Property Panel Submit(21.09.2026 um 07:34 Uhr)
Reverse Engineeringsearch instructions assembly easy (X86,RISCV,AARCH64,etc)(20.09.2026 um 15:44 Uhr)
Sichere ProgrammierungKI half beim Finden: iOS 27 schließt mehr als 100 Sicherheitslücken(21.09.2026 um 06:00 Uhr)
Sichere ProgrammierungWhat Is Rowhammer? How Can Repeated Memory Access Flip Bits in RAM?(21.09.2026 um 07:12 Uhr)
Sichere Programmierungnpm publish Ignores .gitignore: The .npmignore Override Rule(21.09.2026 um 07:15 Uhr)
Sichere ProgrammierungAphelion Editor - A free node-based video / VFX editor(21.09.2026 um 07:21 Uhr)
Sichere ProgrammierungGovernance Attack Surface Review: OKX(21.09.2026 um 07:31 Uhr)
Sichere ProgrammierungJSM Portal Request Create Property Panel Submit(21.09.2026 um 07:34 Uhr)
Reverse Engineeringsearch instructions assembly easy (X86,RISCV,AARCH64,etc)(20.09.2026 um 15:44 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Why Dark Mode Should Not Be a Second CSS File

Reagiere als Erste:r — dein Feedback zählt!

Dark mode often begins with a simple idea:

styles.css
dark.css

The light theme lives in one file.

Dark-mode overrides live in another.

For a small interface, this can appear perfectly reasonable.

But as the application grows, the two files begin to behave like two separate implementations of the same UI.

A component changes in styles.css.

The corresponding rule in dark.css is forgotten.

A new hover state is added.

A focus ring is updated.

A disabled style changes.

Now every improvement to the default interface requires another matching override in the dark-mode file.

Dark mode becomes expensive because the theme is implemented at the component level.

That is the real problem.

Dark mode should not be a second version of your component CSS.

It should be a different set of token values.

The duplicated CSS approach

Imagine a card written like this:

.card {
  background: #ffffff;
  color: #111827;
  border: 1px solid #e5e7eb;
}

To support dark mode, a team may create another rule:

/* dark.css */

.card {
  background: #0f172a;
  color: #f8fafc;
  border-color: #334155;
}

Then a button needs the same treatment:

/* styles.css */

.button-primary {
  background: #2563eb;
  color: #ffffff;
}

.button-primary:hover {
  background: #1d4ed8;
}

And another dark-mode override:

/* dark.css */

.button-primary {
  background: #60a5fa;
  color: #0f172a;
}

.button-primary:hover {
  background: #93c5fd;
}

This works.

But the theme logic is now spread across component selectors.

Every component needs to know how it looks in light mode and dark mode.

That creates duplication.

The files eventually drift

Suppose the default button receives a new focus state:

.button-primary:focus-visible {
  outline: 3px solid #93c5fd;
  outline-offset: 2px;
}

Someone must remember to check whether the dark theme also needs a different focus color.

Then the disabled state changes:

.button-primary:disabled {
  background: #e5e7eb;
  color: #94a3b8;
}

Again, the dark-mode implementation needs another override.

The same issue appears with:

  • hover states
  • pressed states
  • focus rings
  • disabled states
  • selected states
  • borders
  • shadows
  • muted text
  • elevated surfaces
  • alerts
  • inputs
  • placeholders

The problem is not that developers cannot write these overrides.

The problem is that the system requires them to remember the same relationship in multiple places.

That is how theme drift begins.

Keep component CSS theme-independent

A better approach is to write components using semantic tokens:

.card {
  background: var(--color-surface);
  color: var(--color-foreground);
  border: 1px solid var(--color-border);
}

.button-primary {
  background: var(--color-primary);
  color: var(--color-on-primary);
}

.button-primary:hover {
  background: var(--state-primary-hover);
}

.button-primary:active {
  background: var(--state-primary-pressed);
}

.button-primary:focus-visible {
  outline: 3px solid var(--state-primary-focus);
  outline-offset: 2px;
}

.button-primary:disabled {
  background: var(--state-primary-disabled-background);
  color: var(--state-primary-disabled-foreground);
}

There is no dark-mode selector here.

The component does not need one.

It only needs to know the semantic role of each value.

The theme decides what those values should be.

Define the light theme as token values

The default theme can be declared at the root:

:root {
  color-scheme: light;

  --color-background: #ffffff;
  --color-foreground: #111827;

  --color-surface: #f8fafc;
  --color-surface-elevated: #ffffff;
  --color-border: #e5e7eb;
  --color-muted: #64748b;

  --color-primary: #2563eb;
  --color-on-primary: #ffffff;

  --color-warning: #facc15;
  --color-on-warning: #111827;

  --color-danger: #dc2626;
  --color-on-danger: #ffffff;

  --state-primary-hover: #1d4ed8;
  --state-primary-pressed: #1e40af;
  --state-primary-focus: #93c5fd;

  --state-primary-disabled-background: #e5e7eb;
  --state-primary-disabled-foreground: #94a3b8;
}

These variables describe UI roles.

They do not describe a particular component.

--color-surface can be used by cards, dialogs, menus, inputs, and other surfaces.

--color-on-primary defines the readable foreground that belongs on the primary background.

The component receives a contract instead of a collection of hardcoded values.

Dark mode should override the tokens

Dark mode can now change the same semantic roles:

:root[data-theme="dark"] {
  color-scheme: dark;

  --color-background: #020617;
  --color-foreground: #f8fafc;

  --color-surface: #0f172a;
  --color-surface-elevated: #1e293b;
  --color-border: #334155;
  --color-muted: #94a3b8;

  --color-primary: #60a5fa;
  --color-on-primary: #0f172a;

  --color-warning: #facc15;
  --color-on-warning: #111827;

  --color-danger: #f87171;
  --color-on-danger: #450a0a;

  --state-primary-hover: #93c5fd;
  --state-primary-pressed: #bfdbfe;
  --state-primary-focus: #60a5fa;

  --state-primary-disabled-background: #1e293b;
  --state-primary-disabled-foreground: #64748b;
}

The token names remain unchanged.

Only their values change.

The card still uses:

background: var(--color-surface);
color: var(--color-foreground);

The button still uses:

background: var(--color-primary);
color: var(--color-on-primary);

The component contract is stable across both themes.

One component, multiple themes

This is the important architectural difference.

With duplicated theme CSS, the structure looks like this:

Light card CSS
Dark card CSS

Light button CSS
Dark button CSS

Light input CSS
Dark input CSS

With semantic tokens, the structure becomes:

One card component
One button component
One input component

Light token values
Dark token values

The first approach multiplies component implementations.

The second approach keeps the implementation stable and changes the data supplied to it.

That is much easier to maintain.

Use the same semantic vocabulary in every mode

Light and dark themes should expose the same token names.

For example:

background
foreground
surface
surfaceElevated
border
muted

primary
onPrimary

warning
onWarning

danger
onDanger

A theme should not expose darkCardBackground or lightCardBackground to the component.

That would make the component aware of the active mode.

Avoid this:

.card {
  background: var(--light-card-background);
}

[data-theme="dark"] .card {
  background: var(--dark-card-background);
}

Prefer this:

.card {
  background: var(--color-surface);
}

The component asks for a surface.

The active theme provides the correct surface value.

Keep foreground and background pairs together

Dark mode often reveals contrast assumptions that were already present in the light theme.

For example:

.button-primary {
  background: var(--color-primary);
  color: white;
}

This assumes every primary background will be dark enough for white text.

But a dark theme may use a lighter primary color:

:root[data-theme="dark"] {
  --color-primary: #60a5fa;
}

The background changed.

The hardcoded foreground did not.

A safer structure defines the pair:

:root[data-theme="dark"] {
  --color-primary: #60a5fa;
  --color-on-primary: #0f172a;
}

Then:

.button-primary {
  background: var(--color-primary);
  color: var(--color-on-primary);
}

Dark mode should change readable relationships, not just isolated background colors.

Interaction states belong in the theme too

It is not enough to theme the default component state.

Interactive states should also use tokens:

.button-primary:hover {
  background: var(--state-primary-hover);
}

.button-primary:active {
  background: var(--state-primary-pressed);
}

.button-primary:focus-visible {
  outline-color: var(--state-primary-focus);
}

.button-primary:disabled {
  background: var(--state-primary-disabled-background);
  color: var(--state-primary-disabled-foreground);
}

The light theme and dark theme can provide different values for these states.

But the component should continue using the same token names.

This prevents dark-mode behavior from being scattered across component-specific overrides.

Not every token needs a dark-mode version

Colors and some interaction states often change between themes.

Spacing usually does not.

Neither do most radius or font-size tokens.

For example:

:root {
  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 1.5rem;
  --space-xl: 2rem;

  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-full: 9999px;

  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.25rem;
}

These values can remain global.

A clean token structure separates mode-dependent tokens from shared tokens.

const tokens = {
  modes: {
    light: {
      colors: {},
      states: {}
    },
    dark: {
      colors: {},
      states: {}
    }
  },

  spacing: {},
  radius: {},
  fontSizes: {}
};

Dark mode should not duplicate values that do not actually change.

Use a data attribute for explicit themes

A data attribute is a practical way to apply the active theme:

<html data-theme="dark">

Then CSS can scope the token values:

:root {
  /* light token values */
}

:root[data-theme="dark"] {
  /* dark token values */
}

Changing the theme only requires changing the attribute:

document.documentElement.dataset.theme = "dark";

To return to light mode:

document.documentElement.dataset.theme = "light";

Or remove the explicit theme to follow the operating system:

delete document.documentElement.dataset.theme;

Support the system preference

A default system-driven dark theme can also be supported:

:root {
  color-scheme: light;

  --color-background: #ffffff;
  --color-foreground: #111827;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --color-background: #020617;
    --color-foreground: #f8fafc;
  }
}

This means:

  • the system preference is used by default
  • data-theme="light" forces light mode
  • data-theme="dark" can explicitly force dark mode

The exact theme-selection strategy may vary, but the token contract remains the same.

Apply the saved theme before the first paint

A well-structured token system can still flash the wrong theme if the saved preference is applied too late.

If JavaScript reads localStorage only after the application loads, the browser may paint the default theme first.

A small script in the document head can apply the saved theme earlier:

<script>
  (function () {
    try {
      var theme = localStorage.getItem("theme");

      if (theme === "light" || theme === "dark") {
        document.documentElement.dataset.theme = theme;
      }
    } catch (_) {}
  })();
</script>

Place this before the main stylesheet when using a client-side saved preference.

The semantic-token architecture controls how themes are represented.

The early script controls when the correct theme is selected.

Both parts matter.

Component CSS should become boring

Well-structured component CSS is often simple:

.page {
  background: var(--color-background);
  color: var(--color-foreground);
}

.card {
  background: var(--color-surface);
  color: var(--color-foreground);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
}

.text-muted {
  color: var(--color-muted);
}

.button-primary {
  background: var(--color-primary);
  color: var(--color-on-primary);
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
}

.button-primary:hover {
  background: var(--state-primary-hover);
}

There are no dark-mode selectors inside these components.

That is a feature.

The component is responsible for structure and behavior.

The theme is responsible for values.

When separate CSS files may still make sense

Separate stylesheets are not always wrong.

A separately loaded theme file can make sense when:

  • themes are delivered independently
  • a white-label product has large brand-specific assets
  • unused theme payloads must not be shipped
  • themes include more than token-value changes
  • the application supports externally installed themes

But even then, the theme file should ideally contain token declarations rather than duplicate component selectors.

For example:

/* theme-dark.css */

:root {
  --color-background: #020617;
  --color-foreground: #f8fafc;
  --color-surface: #0f172a;
  --color-primary: #60a5fa;
  --color-on-primary: #0f172a;
}

This is still one component implementation with a separately delivered token set.

The problem is not the physical number of files.

The problem is duplicating component CSS for each mode.

A practical structure

A small project could use:

styles/
├── tokens.css
├── components.css
└── app.css

tokens.css:

:root {
  /* shared tokens */
  /* light theme values */
}

:root[data-theme="dark"] {
  /* dark theme values */
}

components.css:

.card {
  background: var(--color-surface);
  color: var(--color-foreground);
}

.button-primary {
  background: var(--color-primary);
  color: var(--color-on-primary);
}

app.css:

@import "./tokens.css";
@import "./components.css";

For a larger system, token values may be generated from JavaScript, JSON, DTCG files, or another shared source.

But the principle stays the same:

components consume semantic roles; themes provide values.

Final thought

Dark mode should not require developers to rebuild every component.

It should not mean:

light card + dark card
light button + dark button
light input + dark input

It should mean:

one card
one button
one input

light token values
dark token values

The rule is simple:

Dark mode should be a different set of token values—not a second implementation of the UI.

When component CSS stays stable, light and dark themes remain easier to maintain, easier to test, and less likely to drift apart.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Why Dark Mode Should Not Be a Second CSS File

Thematisch verwandte Begriffe: Dark, Mode, Should, Second · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94111 | Tencent BrowserSkill through 0.3.0 contains an authentication bypass vul…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick