Ask
29

Colours built from a variable work in dev and come out unstyled in production — what am I misunderstanding?

I have a badge component that takes a colour prop and builds the class from it, something like a template string that produces bg-red-500 or bg-green-500 depending on the value.

In development every badge is correctly coloured. In the production build they are all unstyled — the class is on the element in the DOM, spelled correctly, and there is no rule for it in the stylesheet.

I have checked the content paths in the config and the file is definitely included, because other classes in the same file work fine. It is only the ones I build from the variable.

What is the actual mechanism here? I would like to understand it rather than pasting a fix, because I suspect I have made the same mistake elsewhere.

3 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @full_names_only · 2h ago

    The fix that follows directly: write the complete class names out and select between them.

    A lookup object mapping your prop values to full class strings. Every class appears literally in the file, the scanner finds all of them, and you index into the map at runtime.

    Why this is better than it looks:

    • It is exhaustive by construction. If your prop is typed as a union, the compiler makes you handle every case, so a new variant cannot silently produce an unstyled element.
    • It is greppable. Searching for bg-red-500 finds the place it is used, which a template string breaks completely.
    • It documents the variants in one readable place.

    The rule I apply everywhere now: never build a class name by concatenation. Not for colours, not for spacing, not for grid columns, not for widths. The moment a class is assembled from pieces you are relying on something that does not exist.

    The partial version catches people too — `bg-${color}-500` is obviously broken once you know the mechanism, but so is `${base}-500`, and so is splitting a class across a line break in a way that leaves the text discontinuous.

    Since you suspect you have done this elsewhere: search your source for a backtick or a plus sign near a class attribute. That finds nearly all of them in a few minutes.

    26
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @scanner_reads_text · 2h ago

    The mechanism is simpler and more brutal than people expect: the build never runs your code. It reads your files as text.

    At build time your source is scanned for anything that looks like a class name. Every string it finds that matches a known pattern gets a rule generated. That is the entire process — there is no evaluation, no type information, no understanding of your components.

    So when you write a template string that produces bg-red-500 at runtime, the scanner sees the literal text in your file. It sees bg- and it sees a variable. It never sees bg-red-500, because that string does not exist anywhere in your source. No rule is generated, and the class on the element points at nothing.

    Why it works in development: dev typically generates rules on demand or ships a much broader set, so the rule happens to exist. Production generates only what was found. This is exactly why the failure appears at deploy time, which is the worst moment to discover it.

    The consequence, and it is the rule to carry away: every class you use must appear complete and unbroken somewhere in a scanned file. It does not have to be in the element that uses it. It does have to exist as literal text.

    Once you have that, the fixes below are obvious rather than magic.

    30
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @safelist_last_resort · 2h ago

    There is a safelist, and it is the right answer in exactly one situation: the class genuinely is not known until runtime — a colour stored in a database, a value from an API, something a user picks.

    In that case no amount of writing classes out helps, because the value does not exist at build time. You list the possible classes in the config and they are generated regardless of whether they were found in source.

    Why it is a last resort rather than a general fix:

    • It generates CSS you may not use, which is how a stylesheet quietly grows
    • It is a second place to maintain. Add a variant to the component and forget the safelist, and you are back to unstyled elements with a fix that looks like it should have worked
    • It hides the mistake rather than correcting it, so the next person copies the pattern

    If you do need it, keep it small and specific — enumerate the classes rather than reaching for broad patterns, because a pattern generates a lot more than you think.

    For the genuinely dynamic case there is often a better option: put the value in a CSS custom property and set it as an inline style, and keep the utility classes static. A colour that comes from a database is data, and data belongs in a style attribute rather than in a class name you have to predict at build time.

    20
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report