Skip to main content
ASoc
Tutorial

Tailwind `!important`: 13 Uses in This Codebase, 12 of Them Provably Unnecessary

Compiled this project's own stylesheet to check — a plain leading-[1.2] would already win the cascade over text-3xl's paired line-height, no ! required.

The ASoc Team8 min read

Tailwind's important modifier prefixes a utility with !!leading-[1.2] — and generates every declaration in that class with !important attached, so it wins regardless of source order. Tailwind v4 moved the flag from a suffix (leading-[1.2]! in v3) to a prefix; this codebase already writes the v4 form. It also uses it in exactly thirteen places across 111 products, and compiling this project's own stylesheet shows at least twelve of them didn't need to be there.

The census

$ grep -rho '"[^"]*"' src/components src/app --include=*.tsx | grep -oE '!\S+' | sort | uniq -c | sort -rn
   9 !leading-[1.2]
   2 !leading-normal
   1 !leading-[1.15]
   1 !bg-primary-600"

Thirteen !-prefixed utilities in the entire application tree. Twelve of them override line-height; the thirteenth is a hover: state on a card's CTA pill (hover:!bg-primary-600). One of the twelve lives in a shared atom:

// src/components/atoms/SectionHeading.tsx
export default function SectionHeading({
  as: Tag = "h2",
  className = "text-3xl font-bold !leading-[1.2] text-title-color md:text-[40px]",
  children,
}: { ... }) {
  return <Tag className={className}>{children}</Tag>;
}

The other twelve components carry their own copy because passing a custom className to SectionHeading replaces the default entirely, defaults included, so any organism that wants its own spacing has to re-declare the line-height too:

$ grep -rl '!leading-\[1\.2\]\|!leading-normal\|!leading-\[1\.15\]\|!bg-primary-600' \
    src/components src/app --include=*.tsx
src/components/organisms/Faq.tsx
src/components/organisms/FrameworkLanding.tsx
src/components/organisms/ChangelogList.tsx
src/components/organisms/BlogIndex.tsx
src/components/organisms/Blog.tsx
src/components/organisms/RelatedPosts.tsx
src/components/organisms/ArticleHeader.tsx
src/components/organisms/TemplateDetail.tsx
src/components/molecules/FeatureCard.tsx
src/components/molecules/TechStackCard.tsx
src/components/molecules/PluginCard.tsx
src/components/templates/LegalTemplate.tsx
src/components/atoms/SectionHeading.tsx

Thirteen files, thirteen occurrences — one per file. Every one of them is a page-level or section-level heading except TechStackCard's hover:!bg-primary-600, which isn't a heading at all — it's a button-style hover state, and it belongs in a different category than the other twelve, covered below.

Why a font-size utility needs a line-height override at all

Tailwind v4 pairs a default line-height with every font-size step in its theme:

/* node_modules/tailwindcss/theme.css */
--text-3xl: 1.875rem;
--text-3xl--line-height: calc(2.25 / 1.875);

And the generated utility reads that pairing through a shared custom property:

.text-3xl {
  font-size: var(--text-3xl);
  line-height: var(--tw-leading, var(--text-3xl--line-height));
}

var(--tw-leading, fallback) is the mechanism: if nothing has set --tw-leading, text-3xl falls back to its own paired line-height. A leading-[1.2] utility exists specifically to set that variable:

.leading-\[1\.2\] {
  --tw-leading: 1.2;
  line-height: 1.2;
}

So the question this post actually tests is whether leading-[1.2] — no ! — reliably wins over text-3xl's own paired fallback when both classes sit on the same element.

Compiling this exact stylesheet to find out

$ npx @tailwindcss/cli -i .tw-scratch/in.css -o .tw-scratch/out.css \
    --content ".tw-scratch/**/*.html"
$ grep -n '^\s*\.text-3xl\s*{\|^\s*\.leading-\\\[1\\\.2\\\]\s*{' .tw-scratch/out.css
1780:  .text-3xl {
1837:  .leading-\[1\.2\] {

.text-3xl compiles to line 1780 of the output; .leading-[1.2] compiles to line 1837 — later in the same stylesheet, in a separate rule with equal specificity (one class selector each). Cascade rules for two equal-specificity rules are decided by source order, and Tailwind v4's generator places its utilities in a fixed category order regardless of which order the classes appear in your className string. The production build confirms the same ordering for what this codebase actually ships:

$ grep -o '\.text-3xl{' .next/static/chunks/*.css
$ python3 -c "
c = open('.next/static/chunks/1o7a1vl6zg196.css').read()
print('.text-3xl{', c.find('.text-3xl{'))
print('.!leading-[1.2]{', c.find(r'.\!leading-\[1\.2\]{'))
"
.text-3xl{ 36230
.\!leading-\[1\.2\]{ 37073

.text-3xl at character 36230, the !-prefixed leading rule at 37073 — later, same as the standalone test. A plain leading-[1.2] sitting where the ! one does now would land in the identical spot in the stylesheet and win the cascade on source order alone, with no !important required. This codebase never generates that plain rule, because it never writes it — only the ! form is ever used, so that's the only one Tailwind's content scanner finds and compiles.

So why is the ! there

Nothing in globals.css or mdx-components.tsx sets a competing line-height on these elements — no h1/h2 element selector, no .prose wrapper with higher specificity, no dark-mode variant fighting it. git log on SectionHeading.tsx shows exactly one commit in this repository's history: the initial pixel-faithful port. !leading-[1.2] almost certainly arrived from the source design the storefront's sections were converted from — a !important habit that made sense in whatever CSS the original export produced, kept verbatim under the "preserve exact Tailwind classes when refactoring" rule this codebase follows for ported markup, and never re-tested against Tailwind's own cascade once the port was done. Two of this blog's own earlier posts already warn against exactly this pattern — Tailwind CSS vs. Hand-Written CSS calls reaching for !important "papering over a source-order problem instead of fixing it," and tailwind class gives the same advice for a stacked-utility conflict. This codebase's own headings are the counter-example: twelve real !leading-* utilities, and every one of them is measurably redundant by the compiled output above.

The thirteenth — hover:!bg-primary-600 on TechStackCard — is a different shape entirely. It's overriding a group-hover: variant elsewhere in the same component's class list, and Tailwind compiles hover: and group-hover: as separate rules whose relative order depends on which appears first in your source, not on a fixed category — a genuine case where ! (or reordering the classes) is doing real work, not superfluous inheritance.

When !important is actually the right tool

SituationReach for !important?Why
Two of your own Tailwind utilities set the same propertyNoReorder the classes, or check the compiled output for which one already wins
A paired utility (font-size's line-height, a shorthand's sub-property) needs overridingUsually noA same-category utility written later in your class list already wins, per the compiled stylesheet's fixed ordering
Overriding a third-party component library's own inline styles or high-specificity selectorsYesYou don't control that CSS's specificity or load order
Two of your own variants (hover: vs group-hover:, dark: vs a media query) raceSometimesVerify in the compiled output first — this is the one case where order genuinely isn't guaranteed by category alone
You inherited the ! from a ported design and never checkedNo — audit itCompile the stylesheet and look for the rule; see above

Troubleshooting

SymptomCauseFix
leading-[1.2] has no visible effect next to a text-* utilityLooks like a cascade loss, usually isn'tCheck the compiled CSS — a same-category utility written later already wins in Tailwind v4's fixed ordering; the real cause is often a typo in the class or a missing rebuild
!important on one utility "leaks" into unrelated stylingIt doesn't — Tailwind scopes !important to the declarations inside that one utility's rule, not the whole elementConfirm the symptom is really about a different property entirely
v3-authored code uses leading-[1.2]! (suffix) and doesn't compile under v4v4 moved the modifier to a prefixRewrite as !leading-[1.2]
Global important: true (Tailwind config) marks every utility !importantA blanket setting from a v3-era config, usually to fight a third-party stylesheetScope it to the one utility that needs it instead — see important: '#id' in Tailwind's docs, or just the per-utility ! prefix
Can't tell whether a specific ! is load-bearingGuessing is how thirteen of them accumulated hereCompile with that one class removed and diff the rendered layout, the way this post did

Frequently asked questions

Does ! in Tailwind v4 do anything different from CSS's own !important? No — Tailwind's important modifier is a code-generation shortcut, not a separate mechanism. !leading-[1.2] compiles to line-height: 1.2 !important; on every declaration inside that utility's rule; it's exactly what you'd get hand-writing the CSS yourself.

Why does Tailwind pair a line-height with every font-size step instead of leaving it to leading-*? So text-3xl alone produces sensible, readable type without a second utility — most usages never touch leading-* at all. The pairing is a default, not a lock: any leading-* utility placed in the same class list overrides it through the shared --tw-leading variable, no ! required, as the compiled output above shows.

How do I check whether one of my own ! utilities is actually necessary? Remove the !, rebuild, and look at the rendered page — or do what this post did and grep the compiled stylesheet for both rules' positions. If the non-! version already appears after the utility it's meant to override, the ! was never load-bearing.

Is it worth going back and removing the twelve redundant !leading-* classes in this codebase? Functionally, the rendered output is identical either way, so it's not a visible bug. It is dead weight in every affected className string, and — per this post's own finding — sets a precedent this blog's other Tailwind posts already argue against. Worth a cleanup pass; not worth a hotfix.

Templates in this post

ASoc Beacon (a mobile device management landing page), ASoc Beaker (a science laboratory website) and ASoc Blueprint (an app development agency site) all render SectionHeading with the default !leading-[1.2] audited above.

Browse the full sets: Next.js landing page templates, Tailwind landing page templates. For the general rule this post's exception sits inside, see Tailwind CSS vs. Hand-Written CSS; for the rest of this project's utility-class habits, tailwind class.

Keep reading

Tutorial12 min read

Tailwind Max Width: 62 Usages, 57 Arbitrary, and 96 Lost Pixels

max-w-* reads the --container-* scale in v4, and max-w-md is 448px, not 768px. A census of 62 usages here, plus the viewport band where our layout narrows as it widens.

Read more
Tutorial11 min read

Scroll-Driven Animations in Tailwind v4 Without a JS Library

animation-timeline replaces the scroll-animation library category — behind two guards. The Tailwind v4 setup, the element you must never fade in, and when IntersectionObserver still wins.

Read more