Skip to content

Build1 publisher2 min readPublished

Matching border-t by substring deleted a pure-CSS triangle from a balance puzzle

A one-line !important override was written to suppress a divider. Because it matched by substring it also hit border-teal-500 and the border-t-24 that a pure-CSS triangle is made of. An exact class selector fixed it.

The Engineer · Build desk

Illustration accompanying Matching border-t by substring deleted a pure-CSS triangle from a balance puzzle

What happened

  • One of those matches was border-t-24, the width that builds a pure CSS triangle, so the fulcrum of a balance puzzle assessment vanished and its beam rendered floating over nothing.
  • Neither break could be caught by a test, and the teal bordered circles that lost their top edge were found only when someone eventually noticed them on screen.
  • The neighbouring [class*='bg-card'] rule keeps its substring match, because Tailwind writes opacity modifiers such as bg-card/90 into the class name and no other utility in the framework contains that string.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint A global !important override subtracts a capability from every component underneath it, so a Card that asks for a border gets one that never paints, and the subtraction is invisible where the component is used.
  • exposure The cost lands on whoever joins next, because the rule is discoverable only in devtools at the moment a declaration shows struck through.
  • decision Before a substring selector ships, the question to settle is whether any other class in the framework can contain the string; bg-card passes that test and border-t fails it several times over.
  • precedent The framework owns the answer to that question, so a version bump or a plugin can quietly reclassify a rule that was safe when it was written.

An attribute selector tests one string. `[class*='border-t']` asks whether the value of the whole class attribute contains those characters anywhere in it, and utility class names are a namespace with no delimiters and no guarantee of uniqueness [14]. `border-teal-500` and `border-transparent` are colour utilities [7]. Both contain the divider rule's search string, so both had `border-top: none !important` applied to them [6].

The triangle is the casualty worth studying. The markup is the classic trick: `<div class="border-t-24 border-x-12 border-x-transparent border-t-slate-400"></div>` [10]. A CSS triangle is its borders, so removing the top one does not shrink the shape or change its colour, it deletes the element's only visible geometry [11].

Of the seven classes the old selector matched, one was the intended `border-t` and two more, `border-t-2` and `border-t-4`, were judged harmless; the other four were outside the rule's purpose [7][1].

Nothing warns you at the call site. Put `border border-border` on a Card and it typechecks, the class lands in the DOM, and devtools shows the declaration struck through [5]. Without devtools, all you see is what looks like a very subtle border colour [5].

The post describes the fix as "one character of specificity" [13]. Specificity stayed where it was: an attribute selector and a class selector each contribute one class-level unit, so the two selectors weigh the same [2]. The narrowing is in what they match, because `border-teal-500` and `border-t-24` are different classes that merely share a prefix with the one the rule meant [12]. The new declaration pairs `border-top: none` with an explicit `border-top-width: 0`, which is what the `bg-card` rule already did with `border-width: 0` [1][12].

That `bg-card` rule was deliberate: it replaced four overlapping copies of the same declaration in different places, on cards that are borderless by design [2]. The first rule the author drew out of the incident is to document the consequence alongside the intent [19]. The stylesheet comment does that: "Consequence worth knowing: a border utility on a Card is silently dead. Use a tinted surface for emphasis instead." [4]

The comment was already in place when two engineers lost an afternoon each to the rule, during a provider expansion with several people working in parallel, after which it went into the shared onboarding document as a known hazard [17][3]. The entry points at around line 449 and describes the symptom: option buttons styled from a `bg-card` variant render as bare text with no visible target [18]. "It typechecks and tests green and is visible only on screen." [18]

What to watch

  • A Tailwind upgrade or a plugin that introduces any class containing bg-card would make the surviving substring rule unsafe.
  • Whether the onboarding-doc hazard becomes a lint rule that flags attribute substring selectors in the stylesheet.
  • Whether any remaining [class*=] overrides in the stylesheet get the same exact-class rewrite.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories