
CSS’s :has() can obviate JS

Defaulting to rem (or other responsive) values in height and width media queries is more accessible than defaulting to px (or other static) values.
The language we typically use for design variants shown on smaller and larger viewports focuses on device size: “mobile layout”, “media min-width”. For most [citation needed] designs, we can instead think of these responsive designs as responding to how many default font-size characters fit on one full-width line. Larger viewports can fit more default font-size text on a full-width line. But viewport size isn’t the only thing that affects how many characters fit on a line: browser zoom level and user-customized default font size do too. In most cases [citation needed], it’s desirable to apply “mobile” styles on desktop when zoomed in or when increasing the default font size. (Josh Comeau’s article The Surprising Truth About Pixels and Accessibility has a case study example.) px-value height and width media queries may respond to browser zoom and to user-customized default font size, but rem-value ones do so more reliably.
v4 Note
Tailwind v4, in beta as of this writing, uses rem breakpoints by default.
Tailwind CSS 3’s default screen values —its responsive breakpoint values— are all px sizes. I convert them to rems.
The keys stay the same, so you can drop this into an existing project without breaking things.
Here’s my solution. It supports min-width size values, object values with a min key and/or a max key, and object values with a raw key; and like default Tailwind it generates max-width screen complements for the min-width size value screens. You can try it out in this fiddle.
const defaultTheme = require("tailwindcss/defaultTheme");
const rootFontSizePx = 16;
/**
* pxToRem
* Given a size in pixels, returns a size rems
*
* @param {Number} px Size to convert to rems, in unitless pixels
* @param {Number} rootPx Size rems are relative to, in unitless pixels
* @returns
*/
function pxToRem(px, rootPx = rootFontSizePx) {
const rem = `${px / rootPx}rem`;
return rem;
}
/**
* Given a Tailwind screens configuration, return the equivalent with all pixel values converted to rems.
* @param {object} screens Screen configuration.
* @returns {object}
*/
function remifyScreens(screens) {
return Object.entries(screens).reduce((acc, [key, value]) => {
if (typeof value === "object") {
const keys = Object.keys(value);
if (keys.find((k) => !["min", "max"].includes(k))) {
acc[key] = value;
return acc;
}
const remScreens = remifyScreens(value);
acc[key] = remScreens;
if (keys.length === 1 && keys[0] === "min") {
acc[`min-${key}`] = { raw: `not all and (max-width: ${remScreens})` };
}
return acc;
}
const remScreen = pxToRem(value);
acc[key] = remScreen;
acc[`max-${key}`] = { raw: `not all and (min-width: ${remScreen})` };
return acc;
}, {});
}
module.exports = {
theme: {
screens: () => {
const pxScreens = defaultTheme.screens;
// if needed, redefine defaults here, for example
pxScreens["2xl"] = "1400px";
return remifyScreens({
...pxScreens,
// additional media queries, if any, go here
"update-slow": { raw: "(update: slow)" },
});
},
},
};
Accessible CSS-Only Light/Dark Toggles (with JS persistence as progressive enhancement)
CSS’s :has() can obviate JS

Writing zsh tab completions can be straightforward
How I add tab completion for the zsh command line

Watch for specific added nodes with MutationObserver
MutationObserver makes it easy to watch for the addition of specific nodes, if you know where to drill.