← Design System Index | Component Reference | Token Reference
IGrow Design System — Responsive Guidelines
This document defines how to build responsively for IGrow digital surfaces. All rules apply across HTML/CSS, Tailwind, React, and WordPress unless a stack-specific note says otherwise.
Traffic context. 80% of IGrow traffic is on Android devices. The two dominant screen sizes are 384 × 832 px (Android mobile, portrait) and 1920 × 1080 px (desktop). Every layout decision should be validated at these two anchors first, then at the breakpoint boundaries in between.
1. Mobile-first principle
Write base styles that work at 384px — no media query required. Then layer overrides upward using min-width breakpoints. Never write max-width media queries in product UI; they fight the cascade and produce fragile code.
System chrome exception. The design system's own documentation site and preview pages use
max-widthqueries internally for their navigation drawers and layout switches. This is a pragmatic exception for tooling — it does not apply to product UI built with this system.
/* ✅ Mobile-first */
.ig-listing-grid { grid-template-columns: 1fr; }
@media (min-width: 768px) { .ig-listing-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .ig-listing-grid { grid-template-columns: repeat(3, 1fr); } }
/* ❌ Max-width — avoid */
@media (max-width: 767px) { .ig-listing-grid { grid-template-columns: 1fr; } }
2. Breakpoint reference
Five named tiers, Tailwind-aligned. The CSS custom properties below are for reference — they cannot be used inside @media queries (CSS custom properties don't interpolate there). Use the raw pixel values in CSS or the sm: / md: / lg: / xl: / 2xl: prefixes in Tailwind.
| Tier | Token | min-width |
Typical context |
|---|---|---|---|
| base | — | 0 px | Android mobile, portrait — your primary target |
| sm | --bp-sm |
640 px | Large phone landscape, small tablet |
| md | --bp-md |
768 px | Tablet portrait |
| lg | --bp-lg |
1024 px | Tablet landscape, small laptop |
| xl | --bp-xl |
1280 px | Standard desktop |
| 2xl | --bp-2xl |
1536 px | Wide desktop — covers your 1920 px majority |
Key rule: If a behaviour should apply to the 384 px majority, it belongs in base styles — no media query. If a behaviour should apply to the 1920 px majority, it belongs at 2xl — and it should look intentional, not just "wider".
3. Gutters
Gutters are the horizontal padding applied to page-level containers. Use the canonical tokens — do not pick ad-hoc --space-* values for container edges.
| Token | Value | Apply at |
|---|---|---|
--gutter-mobile |
16 px | base (384 px) |
--gutter-tablet |
24 px | md (768 px+) |
--gutter-desktop |
32 px | lg (1024 px+) |
HTML / CSS
.ig-container {
width: 100%;
padding-inline: var(--gutter-mobile);
}
@media (min-width: 768px) { .ig-container { padding-inline: var(--gutter-tablet); } }
@media (min-width: 1024px) { .ig-container { padding-inline: var(--gutter-desktop); } }
Tailwind
<div class="px-gutter-mobile md:px-gutter-tablet lg:px-gutter-desktop max-w-igrow-lg mx-auto">
…
</div>
React
<div style={{
paddingInline: 'var(--gutter-mobile)',
maxWidth: 'var(--container-lg)',
margin: '0 auto',
}}>
{/* or use a <Container> wrapper component */}
</div>
WordPress (theme.json)
The repo's system/wordpress/theme.json registers spacing via spacingSizes. Add the gutter sizes to that array so the block editor exposes them as presets:
{
"settings": {
"spacing": {
"spacingSizes": [
{ "slug": "gutter-mobile", "name": "Gutter Mobile", "size": "16px" },
{ "slug": "gutter-tablet", "name": "Gutter Tablet", "size": "24px" },
{ "slug": "gutter-desktop", "name": "Gutter Desktop", "size": "32px" }
]
}
}
}
In PHP templates, reference the CSS custom property that theme.json generates:
<div style="padding-inline: var(--wp--preset--spacing--gutter-mobile);">
4. Spacing scale per breakpoint
The design system uses a 4 px base scale (--space-1 through --space-10). When adapting spacing across breakpoints, never jump more than 2 steps between tiers.
| Context | base (384 px) | md (768 px) | 2xl (1536 px) |
|---|---|---|---|
| Section vertical padding | --space-7 (48 px) |
--space-8 (64 px) |
--space-10 (128 px) |
| Card internal padding | --space-4 (16 px) |
--space-5 (24 px) |
--space-5 (24 px) |
| Stack / list gap | --space-3 (12 px) |
--space-4 (16 px) |
--space-5 (24 px) |
| Inline element gap | --space-2 (8 px) |
--space-2 (8 px) |
--space-3 (12 px) |
Why the 2xl step up matters. On a 1920 px screen, inheriting 48 px section padding from mobile makes the layout feel cramped and unintentional. Stepping up to 128 px creates the premium, spacious feel the brand calls for — it also signals to the user that whitespace is deliberate, not accidental.
5. Fluid typography
For headings and display copy, prefer the clamp()-based fluid tokens over static values. These scale continuously with the viewport — no media-query breakpoints needed.
| Token | Range | Use for |
|---|---|---|
--fs-fluid-display |
40 → 72 px | Hero display headlines |
--fs-fluid-hero |
32 → 56 px | Hero titles |
--fs-fluid-h1 |
28 → 40 px | Page <h1> |
--fs-fluid-h2 |
24 → 32 px | Section headings |
--fs-fluid-h3 |
20 → 28 px | Card / sub-section headings |
--fs-fluid-lead |
17 → 20 px | Lead paragraphs below a hero |
Static tokens for body and UI. Body copy (--fs-16), captions (--fs-12), labels (--fs-14), and button text should use fixed sizes. Scaling body text with the viewport degrades readability.
The ceilings are intentional. The fluid tokens cap at defined maxima — display stops at 72 px, h1 stops at 40 px, lead stops at 20 px. These are correct for 1920 px screens. Do not override them upward.
HTML / CSS
h1 { font-size: var(--fs-fluid-h1); line-height: var(--lh-heading); }
.hero__title { font-size: var(--fs-fluid-hero); line-height: var(--lh-tight); }
Tailwind
<h1 class="text-fluid-h1 leading-heading">…</h1>
<p class="text-fluid-lead leading-body">…</p>
React
<h1 style={{ fontSize: 'var(--fs-fluid-h1)', lineHeight: 'var(--lh-heading)' }}>…</h1>
WordPress (theme.json)
{
"styles": {
"elements": {
"h1": { "typography": { "fontSize": "clamp(1.75rem, 1rem + 2.5vw, 2.5rem)" } },
"h2": { "typography": { "fontSize": "clamp(1.5rem, 1rem + 1.8vw, 2rem)" } }
}
}
}
6. Layout
Band + container pattern
Every page section uses a full-bleed band with a constrained inner container. The band takes colour, imagery, or gradient to the screen edge. The container limits content width. This applies at all widths — it is especially important at 1920 px where unconstrained content looks adrift.
<!-- Full-bleed band, constrained content -->
<section class="ig-hero-section" style="background-image: url(…)">
<div class="ig-container"> <!-- constrained wrapper -->
<h1 class="ig-hero-section__title">…</h1>
<p class="ig-hero-section__body">…</p>
</div>
</section>
This applies to the nav, hero, every section band, and the footer. The nav bar always spans the full viewport width. Only the nav's inner content (logo + links) is constrained.
Container sizes
| Token | Value | When to use |
|---|---|---|
--container-sm |
760 px | Focused content: forms, blog posts, CTA bands |
--container-md |
1200 px | Standard pages |
--container-lg |
1400 px | Wide pages: property listings, dashboards |
At 1920 px, max content width is --container-lg. The space outside it is intentional whitespace or band colour — never an empty void to apologise for.
Prose max-width
Body copy, lead paragraphs, and form fields should be capped at approximately 70ch (~680 px) regardless of container width. This is a readability rule — long line lengths slow reading comprehension.
.article-body { max-width: 70ch; }
Column grid
| Breakpoint | Columns |
|---|---|
| base | 4 |
| md | 8 |
| lg+ | 12 |
Property card grid
The property card grid uses a specific column progression:
| Breakpoint | Columns |
|---|---|
| base | 1 |
| md | 2 |
| lg | 3 |
| xl+ | 4 |
<!-- HTML / CSS -->
<div class="ig-grid ig-grid--cards">…</div>
<!-- Tailwind -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-igrow-4">…</div>
Imagery aspect ratios at 2xl
Hero and banner images need explicit aspect ratios at wide viewports to prevent awkward stretching.
| Context | Aspect ratio | Token |
|---|---|---|
| Hero / full-bleed banner | 21 / 9 | --aspect-banner |
| Property card thumbnail | 3 / 2 | --aspect-card |
| Standard hero | 16 / 9 | --aspect-hero |
At 2xl, apply aspect-ratio: var(--aspect-banner) to hero sections so they fill the canvas proportionally rather than growing arbitrarily tall.
Deliberate whitespace at 2xl
Large screens should feel spacious and premium — not sparse. Step up section padding intentionally at 2xl:
.ig-section { padding-block: var(--space-7); } /* 48px — mobile */
@media (min-width: 768px) { .ig-section { padding-block: var(--space-8); } } /* 64px */
@media (min-width: 1536px) { .ig-section { padding-block: var(--space-10); } } /* 128px — 2xl */
7. Component responsive behaviour
For component-specific markup and variants, see the Component Reference. This section covers responsive behaviour only.
Navigation
| Breakpoint | Behaviour |
|---|---|
| base → md | Hamburger toggle visible; nav links hidden; drawer opens as <dialog> |
| lg+ | Full nav links visible; hamburger hidden |
- Mobile nav height: 56 px (
--nav-height-mobile) - Desktop nav height: 72 px (
--nav-height-desktop) - The nav bar is always full-bleed. The inner content is constrained to
--container-lg. - Account for nav height when positioning sticky or full-screen elements:
top: var(--nav-height-mobile)/top: var(--nav-height-desktop).
Hero
| Breakpoint | Behaviour |
|---|---|
| base | Single column; title and body stacked; aspect-ratio: 16/9 minimum |
| md+ | Full-bleed photo hero with overlay; title large-left / body beneath |
| 2xl | aspect-ratio: var(--aspect-banner) (21/9) |
Use --fs-fluid-hero for the title — it handles the 384 px → 1920 px range without breakpoint overrides.
Property card grid
See the column progression in Layout → Property card grid.
Cards inside the grid lock their thumbnail to aspect-ratio: var(--aspect-card) (3/2) at all sizes. Do not allow card thumbnails to resize proportionally with text — the image height should be stable.
Buttons
| Breakpoint | Behaviour |
|---|---|
| base | Full-width (ig-btn--block) for primary CTAs in forms and hero sections |
| sm+ | Auto-width (inline); right-size for the context |
| lg+ | Use ig-btn--lg for primary hero CTAs |
Buttons inside a card footer or listing bar are never full-width — only standalone hero/form CTAs go full-width at mobile.
<!-- Full-width on mobile only -->
<a class="ig-btn ig-btn--primary ig-btn--block sm:w-auto">Request a viewing</a>
Forms
| Breakpoint | Behaviour |
|---|---|
| base | Single column; all fields full-width |
| md+ | Two-column grid for multi-field forms (name + surname, city + province) |
Form containers should use max-width: var(--container-sm) (760 px) — forms should never stretch to --container-lg. Apply inputmode and autocomplete attributes on every field: they directly improve the 384 px Android experience by triggering the correct keyboard and autofilling common values.
<form style="max-width: var(--container-sm)">
<div class="ig-field-group ig-field-group--2col">
<div class="ig-field">…</div>
<div class="ig-field">…</div>
</div>
</form>
Tables
| Breakpoint | Behaviour |
|---|---|
| base → md | .ig-table-wrap enables horizontal scroll; table renders at natural width |
| md+ | Table fits the container; no horizontal scroll needed |
Always wrap .ig-table in .ig-table-wrap. Never hide table columns on mobile — horizontal scroll is preferable to missing data. If a table has more than 6 columns, consider a card-based alternative for mobile.
Typography headings
Always use fluid tokens for h1–display-level headings. Use static tokens for body, labels, captions, and UI text.
| Element | Token |
|---|---|
| Display / hero | --fs-fluid-display, --fs-fluid-hero |
<h1> |
--fs-fluid-h1 |
<h2> |
--fs-fluid-h2 |
<h3> |
--fs-fluid-h3 |
| Lead paragraph | --fs-fluid-lead |
| Body copy | --fs-16 (static) |
| Labels / captions | --fs-14 / --fs-12 (static) |
8. Testing checklist
Test in this order — the two majority sizes first, then the boundaries.
- 384 px — no horizontal scroll; all content accessible; primary CTA full-width; body copy legible
- 1920 px — content constrained by container-lg; whitespace feels intentional; no text larger than fluid ceilings; hero aspect ratio correct
- 640 px (sm boundary) — inline buttons switch from full-width to auto
- 768 px (md boundary) — nav links appear; gutter increases; form goes 2-col
- 1024 px (lg boundary) — full desktop nav; gutter at desktop size
- 1280 px (xl boundary) — 4-column card grid kicks in
- Rotate 384 px device to landscape (667 px wide) — layout should not break
- Verify
prefers-reduced-motion— all transitions should collapse to near-zero