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.
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
| Value | Sits in text flow | Width / height | Vertical margin | Line box |
|---|---|---|---|---|
inline | Yes | Ignored | Ignored | Wraps across lines |
inline-block | Yes | Applied | Applied | One unbreakable box |
block | No — starts a new line | Applied | Applied | Fills its container's width |
inline-flex | Yes | Applied | Applied | One 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
| Symptom | Cause | Fix |
|---|---|---|
mt-* / mb-* does nothing on a <span> or <a> | Inline elements discard vertical margin | Add inline-block (or inline-flex, or block) |
h-* / w-* ignored on an inline element | Same rule — inline boxes size to their content | inline-block |
Mysterious gap between two inline-block elements | The whitespace between the tags in your HTML is a rendered space — inherent to inline layout, not a bug | Use flex/inline-flex with gap-*, which has no such artifact |
inline-block items won't vertically align with each other | They align on the text baseline by default | align-top / align-middle, or switch to inline-flex with items-center |
Vertical padding on inline overlaps the lines above and below | Inline boxes don't grow the line box for vertical padding | inline-block — it does |
Element with contents won't take a background or border | It has no box by definition | Move the style to a child, or drop back to block |
hidden doesn't hide at one breakpoint | Another display utility later in the cascade wins at that width | Use 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.
