In CSS, why does width: min(100%, 60ch) behave like a maximum width rather than a minimum one?
answer
- the name describes the arithmetic
- smaller value wins the comparison
- a ceiling, not a floor
- equivalent to width plus max-width
- max() is the mirror: a floor
basics
~20 smin() returns the smallest of its arguments at any moment, so it acts as a ceiling: the element is capped at 60ch and shrinks to 100% of its container when that is narrower. The function name describes the comparison performed, not the design intent.
solid answer
~40 sThe function names describe the arithmetic, not the role the value plays in the design. `min()` picks the smallest argument, so whichever bound is smaller wins — the element can never exceed either one, which reads as a maximum. `width: min(100%, 60ch)` therefore means "at most 60 characters wide, and never wider than the parent", the same effect as `width: 60ch; max-width: 100%` in one declaration. `max()` is the mirror image: it picks the largest argument, so it enforces a floor — `padding-inline: max(1rem, 5vw)` guarantees at least 1rem of padding and lets it grow on wide screens. The practical value is that these work in places with no `max-`/`min-` counterpart property, such as `padding`, `gap`, `margin`, or a grid track size.
go deeper
Learn the pairing by heart: min() gives you a ceiling and max() gives you a floor. Be able to say that width: min(100%, 60ch) means at most 60ch and never wider than the parent.
Explain that the functions are arithmetic over their arguments, evaluated continuously as layout changes, and show the longhand equivalence with width plus max-width as well as a case such as padding where no longhand exists.
Demonstrate where these earn their keep in production CSS — bounding grid track floors so auto-fill grids do not overflow, guaranteeing minimum padding against env() insets — and be precise about what each percentage resolves against.
Treat these as the constraint vocabulary of a sizing system: bounds expressed once inside tokens keep component authors from re-deriving measure and spacing limits per feature, and make the system's limits reviewable in a single file.
## The naming trap Every candidate who meets `min()` for the first time reads it as "the minimum size of this thing". It is not. `min()` and `max()` are arithmetic functions: they take a comma-separated list of calculations and return the smallest or the largest of them. The confusion comes from mapping the *operation* onto the *intent*, and the two are inverted: - `min(a, b)` returns the smaller value ⇒ the result can never exceed either argument ⇒ it behaves as a **maximum / ceiling**. - `max(a, b)` returns the larger value ⇒ the result can never fall below either argument ⇒ it behaves as a **minimum / floor**. ## Working the example ```css .prose { width: min(100%, 60ch); } ``` The `ch` unit is the advance width of the `0` glyph in the element's font, so `60ch` is a rough stand-in for a comfortable line length. The `100%` resolves against the containing block's width. - In a 1200px-wide container where `60ch` computes to about 550px, `min()` returns 550px. The measure is capped. - In a 400px-wide container, `100%` is 400px and is now the smaller argument, so `min()` returns 400px. The element shrinks with its parent instead of overflowing. That is exactly the pairing `width: 60ch; max-width: 100%`, collapsed into one declaration with one source of truth. ## max() as a floor ```css .section { padding-inline: max(1rem, 5vw); } ``` On a 320px viewport `5vw` is 16px, equal to `1rem`; on a 1600px viewport it is 80px. The `1rem` argument guarantees the padding never collapses to a hairline on tiny screens. A frequent production use is honouring device insets while keeping a minimum: `padding-inline: max(1rem, env(safe-area-inset-left))`. ## Why not just use min-width / max-width? Because most properties have no paired constraint property. There is no `max-padding`, no `min-gap`, no `max-margin`, and no way to bound a single grid track from a separate declaration. `min()` and `max()` bring constraint semantics to every value slot: ```css .grid { /* prevents the track floor from overflowing narrow viewports */ grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr)); gap: max(0.75rem, 2vw); } ``` That `minmax(min(100%, 16rem), 1fr)` is the canonical fix for `auto-fill` grids: without the inner `min()`, a 16rem track floor overflows any viewport narrower than 16rem. ## More than two arguments, and nesting Both functions accept any number of comma-separated arguments, and each argument is a full math expression, so no `calc()` wrapper is required: ```css .thing { width: min(100%, 60ch, 40rem); } .title { font-size: max(1.25rem, 1rem + 2vw); } ``` When you find yourself nesting one inside the other — `max(1rem, min(4vw, 2rem))` — that is precisely `clamp(1rem, 4vw, 2rem)`, and the `clamp()` form is easier to read. ## Pitfalls **Reaching for the function whose name matches the intent.** "I want a minimum padding" invites `min()`, which produces the opposite of the intent. Say the arithmetic out loud instead: *which value do I want to win?* **Forgetting what a percentage resolves against.** In `min(100%, 60ch)` the `100%` is a percentage of the containing block for that property — the parent's content width for `width`. Inside a flex or grid item, that containing block may not be what you assumed. **Comparing incompatible types.** All arguments must resolve to the same type. `min(100%, 60ch)` is fine because both are lengths after resolution; `min(100%, 1.5)` is invalid because a bare number is not a length. **Assuming a comparison happens once.** The comparison is re-evaluated as the layout changes, so the "winning" argument can swap as the container resizes — that is the whole point of the technique, but it means the value cannot be reasoned about from the stylesheet alone. ## How to answer it crisply "`min()` returns the smaller of its arguments, so it caps the value — it is a maximum in effect. `max()` returns the larger, so it is a floor. `width: min(100%, 60ch)` is `width: 60ch; max-width: 100%` in one declaration, and the real win is that it works on properties like `padding` and `gap` that have no `max-` counterpart."
- What advantage does min() have over just writing width: 60ch; max-width: 100%?Two things. It keeps one source of truth, so a bound cannot be overridden independently and drift out of sync. More importantly it works where no paired constraint property exists — there is no `max-padding`, `min-gap` or `max-margin`, and a grid track cannot be bounded from a separate declaration, so `padding: max(1rem, 5vw)` or `minmax(min(100%, 16rem), 1fr)` have no longhand equivalent.
- How does nesting min() inside max() relate to clamp()?They are the same operation. `max(A, min(B, C))` is the specification's own definition of `clamp(A, B, C)`, so any nested pair reduces to a single `clamp()` with the minimum first and the maximum last. Prefer the `clamp()` form for readability; reach for standalone `min()` or `max()` when you genuinely need only one bound.
- Why does minmax(min(100%, 16rem), 1fr) appear so often in auto-fill grids?Because a bare `minmax(16rem, 1fr)` sets a track floor of 16rem, and on a viewport narrower than 16rem that floor forces horizontal overflow. Wrapping the floor in `min(100%, 16rem)` lets it collapse to the container width on very narrow screens, so the grid degrades to a single full-width column instead of scrolling sideways.
saying these in an interview costs you the question
- Saying min() sets a minimum size for the element
- Assuming max() caps a value at an upper bound
- Claiming the functions only accept two arguments
- Thinking calc() is required around arithmetic inside min() or max()
- Believing the winning argument is chosen once at parse time