Component sizing contract guidelines — intrinsic size vs layout contract, block-size owned by component, inline-size owned by container. Use when building reusable UI components, deciding width/height strategy, reviewing component sizing, or when a component's size behaves unexpectedly in different layout contexts.
Two layers determine every component's final size:
The core heuristic:
block-size → component decides (content, padding, line-height, min-height)
inline-size → container decides (available space, grid/flex distribution)
This is a default priority, not an absolute rule. It reflects the fact that in normal flow, inline-size resolves from available space while block-size resolves from content.
Note that auto and 100% are not interchangeable for inline-size. A block-level element with width: auto fills available space minus its own margins — the browser resolves it. width: 100% forces the content box to exactly match the containing block's content width, which can cause overflow when the element has padding, border, or margin (unless box-sizing: border-box absorbs padding and border). In flex/grid contexts, auto means "use my content size" while means "use my containing block's size" — entirely different behaviors.
100%Button, Input, Tag, Badge, Avatar, IconButton
padding-block, line-height, font-size, min-block-size, and size variants (sm / md / lg).auto (hug content). Provide an explicit fill variant when full-width is needed — never default to w-full.align-items: stretch, which will override min-block-size and pull the component taller. Defend with an explicit block-size on the component root or align-self: center.Typical height scale for controls:
| Variant | Height | Use case |
|---|---|---|
sm | 32px | Dense UI, toolbars |
md | 36–40px | Default desktop |
lg | 44px | Mobile touch target (Apple HIG / WCAG AAA) |
Width API pattern:
type Width = 'auto' | 'fill';
auto → inline-size: autofill → inline-size: 100%Card, List, Section, FormGroup, Panel
100% — fills the parent. The parent (or a page shell) controls max-inline-size. This assumes normal flow; inside a flex row, use flex: 1 instead of width: 100% to fill available space.auto — content determines height. Only constrain with max-block-size + overflow: auto when the container must scroll.Dialog, Drawer, Sidebar, Popover, Toast
min-inline-size, max-inline-size, and sometimes explicit block-size are expected.paddinggapline-height / font-sizemin-block-sizemin-inline-size / max-inline-size (for safety bounds, e.g. Button min-width, Input min-width, Popover max-width)max-inline-sizePrinciple: the component defines "what it looks like and the minimum space it needs"; the container defines "how much space it gets in this context".
Many sizing failures in practice are not about missing width — they are about flex/grid item defaults.
min-width: auto on flex and grid itemsBy spec, flex items default to min-width: auto, which prevents them from shrinking below their content size when overflow is visible (the default). Long text or large children can blow out the layout. Grid items have the same automatic minimum size behavior.
Fix: apply min-w-0 on items that should allow content to truncate. Setting overflow: hidden on the item also bypasses the automatic minimum, but min-w-0 is the more surgical fix — it doesn't clip content on the cross axis.
Side elements (status indicator, action button) can shrink when the main content pushes against them.
Fix: apply shrink-0 on elements that must maintain their intrinsic size.
Typical row pattern:
<div className="flex items-center gap-3 px-4 py-3">
<Icon className="shrink-0" />
<span className="min-w-0 truncate">{label}</span>
<StatusDot className="shrink-0" />
</div>
Storybook is a verification tool, not the sizing strategy itself. Use it to prove the component's sizing contract holds across contexts:
export const NarrowContainer: Story = {
decorators: [
(Story) => (
<div className="w-64">
<Story />
</div>
),
],
};
export const WideContainer: Story = {
decorators: [
(Story) => (
<div className="w-[720px]">
<Story />
</div>
),
],
};
export const FlexRow: Story = {
decorators: [
(Story) => (
<div className="flex gap-2">
<Story />
<Story />
</div>
),
],
};
If a component only looks right inside one specific container width, its sizing contract is underspecified.
| Component type | inline-size | block-size |
|---|---|---|
| Leaf | auto (provide fill variant) | Component-defined via size token |
| Container | 100% | auto (opt-in max-block-size + overflow) |
| Overlay | Component-defined min/max | Component-defined or auto |
| Page shell | 100% + max-inline-size | Viewport-driven |