Skip to content

Responsive helpers #9

Description

@minimaldesign

Assessment

Worth doing, at a deliberately trimmed scope that now includes spacing. Revised 2026-07-23 after measuring against the real dist/ bundle.

Original decision (2026-07-17) was display + text-align only, with spacing explicitly out of scope. That has been reversed: without responsive spacing the feature has little practical value, and the measured cost of a trimmed spacing set is acceptable. Naming stays plain suffix style (.display-none-md, .mt-md2-lg), matching the framework's existing kebab/property-prefixed helper names.

Effort: M.

What changed since the original plan

  • Spacing helpers are now generated by src/tools/generate.help.spacing.cjs: 16 properties x 28 sizes = 448 rules, ~24 kB raw. That is the base any responsive variant multiplies.
  • There is now a real distributable (npm run build:css -> dist/mcss.css, dist/mcss.min.css), so bloat can be measured rather than estimated.
  • Spacing tokens are fixed px, not fluid clamp() values. Responsive spacing therefore adds capability the framework genuinely lacks. The layout scaffolds (global.layout.css) and --section-spacing cover page-level and section-level cases, but nothing covers per-element spacing changes across breakpoints.
  • Still true: the framework has no display helpers at all (help.layout.css covers overflow/position/visibility/z-index only), so those get added as part of this.

Measured cost

Baseline dist/mcss.min.css: 145.2 kB minified, 22.0 kB gzip, 16.2 kB brotli.

Each scope below was generated, appended inside @layer helpers, and re-minified through the same esbuild pipeline build-css.mjs uses. Breakpoints are sm/md/lg, mobile-first, in every row.

Scope Rules added Minified Gzip Brotli
Display + text-align only (original scope) 42 +2.1 kB (+1.4%) +0.3 kB +0.1 kB
+ spacing, full cross-product (16 props x 28 sizes) 1386 +62.8 kB (+43%) +6.5 kB (+30%) +1.9 kB (+12%)
+ spacing, minus w/h (14 x 28) 1218 +56.5 kB +5.9 kB +1.8 kB
+ spacing, logical props only (10 x 28) 882 +41.3 kB +4.5 kB +1.3 kB
+ spacing, logical props, sizes 0-xl3 (10 x 16) 522 +23.7 kB (+16%) +2.7 kB (+12%) +0.9 kB (+5.5%)

Two costs point in opposite directions. Over the wire it is nearly free: the CSS is repetitive enough that brotli eats it, and even the full 1386-rule version costs 1.9 kB. Raw size is the real bloat: full responsive spacing adds 63 kB minified (+43%) and would grow the spacing helpers alone to ~87 kB, roughly 40% of the framework. For a copy-it-you-own-it framework whose files are meant to be read, that is disproportionate, and not every consumer serves brotli.

Decision: the trimmed set (522 rules, +16% minified)

Ship display helpers, text-align variants, and spacing variants restricted to:

  • Properties (10): m, mt, mb, mis, mie, p, pt, pb, pis, pie
  • Sizes (16): 0, xs1-3, sm1-3, md1-3, lg1-3, xl1-3
  • Breakpoints (3): sm (>=480px), md (>=768px), lg (>=1024px)

The trims are principled, not just thrifty:

  • No responsive ml/mr/pl/pr (base classes stay): responsive layout is exactly where direction-aware logical properties belong. Removes 4 of 16 properties.
  • No responsive w/h: width-at-breakpoint is a layout concern the grid system and layout scaffolds already own. .w-md1-lg is a smell.
  • No mega/giga/tera: section-scale spacing is already tokenized (--section-spacing) and themeable at whatever breakpoints a consumer wants.

Escape hatch: consumers who want pl-* variants or tera sizes edit the arrays in generate.help.spacing.cjs and regenerate in their own copy. That is the most mCSS-shaped answer available, and it must be documented as part of this issue.

Implementation plan

1. Base display helpers (new)

In src/styles/framework/help.layout.css (already imported layer(helpers) in mcss.css, no import changes needed), add a DISPLAY section following the existing long-form naming:

.display-none, .display-block, .display-inline, .display-inline-block, .display-flex, .display-grid, plus short aliases .d-none etc., mirroring the .of-*/.overflow-* alias pattern. Property-prefixed names dodge the .grid class collision with the grid system.

Naming wrinkle to document: .display-sm, .display-md, .display-lg etc. already exist in help.typography.css as font-size helpers, so the .display-* prefix does double duty once .display-none-md lands. No actual collisions (display-property values never look like size tokens), but it is an argument for documenting the short .d-* aliases prominently.

2. Responsive variants

  • Semantics: mobile-first. A -{bp} suffix applies at that breakpoint and above, using the existing @custom-media tokens from settings.media-queries.css (--sm >=480px, --md >=768px, --lg >=1024px).
  • Covered helpers:
    • display helpers -> .display-none-md, .d-flex-lg, ... (18 rules, aliased so 18 selectorsx2 names)
    • text-align helpers in help.typography.css -> .text-center-md, .text-start-lg, ... (18 rules)
    • spacing helpers -> .mt-md2-lg, .p-sm1-md, ... (480 rules: 10 props x 16 sizes x 3 bp)
  • Down-direction needs no extra classes: class="display-none display-block-md" hides below md. Document this idiom.
  • Order matters within each file: emit @media blocks in ascending breakpoint order after the base classes so larger breakpoints win at equal specificity.
  • Names stay unambiguous: size tokens always carry a digit (md2), breakpoint suffixes never do, so .mt-md2-lg parses cleanly.

3. Generator

Extend src/tools/generate.help.spacing.cjs with a responsive pass driven by explicit responsiveProperties / responsiveSizes / breakpoints arrays, kept separate from the existing properties/sizes arrays so the base set and the responsive subset can diverge. Emit the @media blocks after all base rules in the same file. Keep the generated-file header comment pointing at the script.

4. Explicitly still out of scope

Responsive color, opacity, typography-size, and ratio helpers. Responsive w/h and physical-direction margin/padding. Sizes above xl3. The grid system has its own responsive story (col/span attributes, plus #10).

5. Docs & verification

  • src/content/docs/helpers.mdx: new "Responsive helpers" section covering the naming rule, mobile-first semantics, the hide/show idiom, the covered-property/size table, and how to regenerate a wider set via the generator script.
  • agents/css.md: record the -{bp} suffix convention and the deliberate property/size restriction.
  • Future-proofing: suffix names are plain strings (no escaping), and a single safelist regex /-(sm|md|lg)$/ covers them when PurgeCSS returns (companion issue Re-evaluate and re-enable PurgeCSS #13, currently a "don't adopt").
  • Verify: dev server, resize across 480/768/1024, confirm variants flip at the right widths and helpers still beat component styles (helpers is the last layer). Re-run npm run build:css and confirm the minified delta lands near the projected +23.7 kB.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions