How to Programmatically Enable and Disable CSS Stylesheets Reliably
The Problem with styleSheet.disabled
When building a theme switcher, toggling the disabled property on a stylesheet or its corresponding HTML element seems like the perfect solution. However, many developers run into a frustrating issue: while disabling a stylesheet works flawlessly, re-enabling it often fails silently or behaves inconsistently, particularly in Chromium-based browsers (like Chrome and Edge).
Why does this happen?
According to the CSSOM and HTML specifications, the disabled property is notoriously quirky. Browsers heavily optimize rendering by unloading stylesheets that are disabled. When you programmatically set disabled = false, the browser may fail to re-evaluate or re-apply the CSS rules because it doesn't trigger a reflow or stylesheet reload correctly.
The Best Solutions for Reliable Theme Switching
1. The Modern Standard: CSS Custom Properties (Highly Recommended)
Instead of enabling and disabling entire stylesheets, the modern industry standard is to use CSS Custom Properties (CSS variables) scoped to a data attribute on the <html> or <body> element. This approach is highly performant, avoids Flash of Unstyled Content (FOUC), and is incredibly easy to maintain.
/* global.css */
:root {
--bg-color: #ffffff;
--text-color: #000000;
}
[data-theme="dark"] {
--bg-color: #121212;
--text-color: #ffffff;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
}Now, switching themes in JavaScript is as simple as updating a single attribute on the document root:
function setTheme(themeName) {
document.documentElement.setAttribute("data-theme", themeName);
}2. Dynamically Adding and Removing Stylesheets
If your stylesheets are too large to bundle together, or if you absolutely must load separate physical files, do not rely on the disabled property. Instead, dynamically append and remove the <link> element from the document <head>. This forces the browser to reliably load and apply the styles.
function loadTheme(themeName) {
// Remove existing theme stylesheet
const existingLink = document.getElementById("theme-stylesheet");
if (existingLink) {
existingLink.remove();
}
// Create and append the new theme stylesheet
const link = document.createElement("link");
link.id = "theme-stylesheet";
link.rel = "stylesheet";
link.href = `/themes/${themeName}.css`;
document.head.appendChild(link);
}3. The Correct Workaround for the disabled Property
If you must use the disabled property approach, ensure your HTML is correctly configured. For alternate stylesheets to be toggled properly, they should be declared with rel="alternate stylesheet" in your markup. Additionally, toggling the disabled property on the element node rather than the stylesheet object tends to have better cross-browser compatibility:
// HTML markup example:
// <link rel="stylesheet" title="light" href="light.css">
// <link rel="alternate stylesheet" title="dark" href="dark.css" disabled>
function switchTheme(themeName) {
const links = document.querySelectorAll("link[rel*='stylesheet']");
links.forEach(link => {
// Enable if it matches the theme, disable otherwise
link.disabled = (link.title !== themeName);
});
}By setting link.disabled = false on the DOM node directly, you force the browser to evaluate the element's state, resulting in much more consistent behavior across Chrome, Firefox, and Safari.