Skip to main content
ASoc
Tutorial

Tailwind `inline-block`: 3 Uses in 257 Display Utilities, and What Replaced It

Counted every display utility in this codebase: inline-flex took 53, inline-block kept 3 — and all three are the same case, a vertical margin on inline text.

The ASoc Team8 min read

inline-block sets display: inline-block: the element sits in text flow like a word, but accepts width, height and — the part that matters — vertical margin and padding. In this storefront it survives in exactly 3 of 257 display-utility uses. inline-flex took 53. Both facts have the same explanation, and it tells you when to still reach for it.

The census

Every className string in this project's application source, tokenized, variants stripped, counted by display utility:

$ node scripts/audit/count-display-utilities.mjs
utility        uses  files
flex            141   58
inline-flex      53   29
block            27   15
grid             23   19
hidden            6    4
inline-block      3    3
inline            3    2
contents          1    1
TOTAL          257

257 display utilities across a codebase of 92 components serving 111 product pages. flex and grid together are 164 of them — 64%. inline-block is three.

A plain grep can't produce that table, which is why the script exists: block, table and grid are ordinary English words that appear in prose, prop names and comments, and inline-flex contains inline. The script reads only the contents of className attributes and strips variant prefixes (md:, dark:, group-hover/media:) before matching, so each count is a real layout decision rather than a substring.

That ratio is the story. inline-block was the standard tool for "in flow, but boxed" for most of CSS's history, and a modern utility-first codebase barely needs it.

What the three values actually do

ValueSits in text flowWidth / heightVertical marginLine box
inlineYesIgnoredIgnoredWraps across lines
inline-blockYesAppliedAppliedOne unbreakable box
blockNo — starts a new lineAppliedAppliedFills its container's width
inline-flexYesAppliedAppliedOne box, children flexed

The row that catches people is inline. display: inline does not ignore all spacing — horizontal margin and padding work fine. It ignores width, height, and vertical margin, and it lets the element break across lines. So:

<!-- h-5 and the vertical padding do nothing here -->
<span class="inline h-5 py-4 px-2">label</span>

<!-- all four apply -->
<span class="inline-block h-5 py-4 px-2">label</span>

That's the whole distinction, and it is why the three real inline-block uses in this codebase look the way they do.

Where it survives, and why all three are the same case

$ grep -rn 'inline-block' src/components src/app --include=*.tsx
src/components/organisms/Footer.tsx:22:   className="mb-6 inline-block"
src/components/molecules/LicenseSummaryCard.tsx:29:   className="mt-4 inline-block text-sm font-medium text-primary hover:underline …"
src/components/atoms/SectionLabel.tsx:16:   className={`inline-block text-lg font-medium text-primary ${className}`.trim()}

Three files. Look at what each one does with it:

// src/components/atoms/SectionLabel.tsx — the eyebrow above a section heading
<span className="inline-block text-lg font-medium text-primary">
  {children}
</span>
// src/components/molecules/LicenseSummaryCard.tsx — a link with top margin
<Link className="mt-4 inline-block text-sm font-medium text-primary hover:underline" …>
// src/components/organisms/Footer.tsx — the logo link with bottom margin
<Link className="mb-6 inline-block" href="/">

Two of the three carry a vertical margin utility — mt-4, mb-6 — on an element that is inline by default. A <span> and an <a> are both inline elements; margin-top and margin-bottom on them are silently discarded. Adding inline-block is what makes the margin exist. Remove the utility and the spacing quietly disappears with no error, no warning, and no visible cause.

The third, SectionLabel, is the same reason one step removed: it is a shared atom whose callers append their own spacing through className, so it has to be able to accept a vertical margin it doesn't know about yet.

So the rule this codebase converged on without anyone writing it down: inline-block is for putting vertical space around something that belongs in text flow. That is the case flexbox does not cover, because you cannot flex an element that needs to sit inside a sentence.

Why inline-flex took the other 53

Every one of the 53 inline-flex uses is doing what inline-block used to be stretched to do: an element that shouldn't start a new line, whose children need aligning.

// src/components/molecules/PreviewModal.tsx
className="inline-flex h-10 items-center justify-center gap-1.5 rounded-lg
           border border-stroke-tertiary bg-white px-3 text-sm font-medium …"

That's a button. Before flexbox it would have been inline-block plus text-align: center, plus line-height equal to the height to fake vertical centering, plus margins between the icon and the label. inline-flex replaces all four with items-center justify-center gap-1.5 — and unlike the line-height trick, it still centers correctly when the label wraps to two lines.

gap is the specific thing that ended the old pattern. Spacing between an icon and its label used to mean a margin on one of them, which then had to be un-set for right-to-left layouts or when the icon moved to the other side. gap-1.5 is symmetric and direction-agnostic.

So: if the box has children to align, inline-flex. If it is a lone piece of text or an image that needs vertical margin, inline-block. The 53-to-3 split is just how often each of those comes up.

The single contents use is the interesting outlier

$ grep -rln 'contents' src/components --include=*.tsx
src/components/organisms/TemplateDetail.tsx

display: contents removes the element's own box while keeping its children in the layout — the children get promoted into the grandparent's formatting context. On the product page it solves a problem no other display value can:

Below md, the product page needs the editions block to appear above the prose, but the buy column is a single grid child containing several blocks. Ordering grid items only works on direct children. Putting contents on the buy column dissolves its box, so its inner blocks become orderable grid items themselves — which is what lands "Live preview" directly under the carousel on a phone, from one picker instance rather than a second duplicated trigger.

It is worth knowing about and worth using sparingly: an element with display: contents has no box, so it cannot take a background, a border, padding, or a size, and historically it had accessibility bugs in several browsers for elements with implicit semantics (those are largely fixed in current engines, but the rule of thumb is to apply it to a plain <div> wrapper, not to a <ul> or a <button>).

Troubleshooting

SymptomCauseFix
mt-* / mb-* does nothing on a <span> or <a>Inline elements discard vertical marginAdd inline-block (or inline-flex, or block)
h-* / w-* ignored on an inline elementSame rule — inline boxes size to their contentinline-block
Mysterious gap between two inline-block elementsThe whitespace between the tags in your HTML is a rendered space — inherent to inline layout, not a bugUse flex/inline-flex with gap-*, which has no such artifact
inline-block items won't vertically align with each otherThey align on the text baseline by defaultalign-top / align-middle, or switch to inline-flex with items-center
Vertical padding on inline overlaps the lines above and belowInline boxes don't grow the line box for vertical paddinginline-block — it does
Element with contents won't take a background or borderIt has no box by definitionMove the style to a child, or drop back to block
hidden doesn't hide at one breakpointAnother display utility later in the cascade wins at that widthUse the variant form — hidden md:flex, not hidden plus md:flex on separate elements

Frequently asked questions

Is inline-block deprecated? No. It is a current, fully supported CSS value and there is no replacement for what it does — putting a sized, vertically-spaced box inside a line of text. It is simply needed far less often now that flexbox and grid handle the layouts it used to be conscripted for. Three uses out of 305 here is low usage, not obsolescence.

Should I replace my inline-block elements with inline-flex? Only where the element has children that need aligning. inline-flex establishes a flex formatting context for the children, which changes how they lay out — on a single text node that is a no-op with extra machinery, and it will collapse the whitespace behaviour your text may depend on. For a lone label or an image with a margin, inline-block is the smaller, more accurate answer.

Why does Tailwind have inline, inline-block and inline-flex as separate utilities instead of a modifier? Because they are three distinct values of one CSS property, and Tailwind's utilities map one-to-one onto CSS values rather than inventing an abstraction over them. That mapping is why the census above is possible at all: each class corresponds to exactly one declaration, so counting classes counts real layout decisions.

How do I get rid of the whitespace gap between inline-block elements without switching to flex? The historical answers all have costs — removing the newline between the tags, using HTML comments to swallow it, or setting font-size: 0 on the parent and restoring it on the children. All of them are workarounds for a quirk of inline layout. Flexbox with gap-* doesn't have the quirk, which is the honest reason the pattern faded.

Templates in this post

ASoc Coin (an online-banking marketing site), ASoc Compound (an automated-investing landing page) and ASoc Cortex (an AI-agency site) all render the SectionLabel atom audited above, inline-block and all.

Browse the full sets: Next.js landing page templates, Tailwind landing page templates. For how these utilities interact across screen sizes, see Tailwind breakpoints; for when a utility genuinely needs to override another, Tailwind !important.

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