breakouts 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # breakouts
2
+
3
+ ## 0.8.2
4
+
5
+ ### Patch Changes
6
+
7
+ - d343b33: The README is rewritten from the source: every class, custom property, variable and default is checked against the code, with the grid's measured widths, the entry points, the full colour token and utility lists, themes and their limits, and customisation at build time and at runtime. The demo site is rebuilt as a Breakouts grid at work, with live widths, a class picker at any width and the full reference. No change to the CSS.
package/README.md CHANGED
@@ -1,213 +1,402 @@
1
1
  # Breakouts
2
2
 
3
- Breakouts is a minimal, modular layout framework for SCSS/CSS that gives you just the right amount of structure: full-bleed containers, responsive grids, spacing, theming, and a clean set of utilities.
3
+ [![npm](https://img.shields.io/npm/v/breakouts?style=flat-square)](https://www.npmjs.com/package/breakouts)
4
+ [![License: MIT](https://img.shields.io/npm/l/breakouts?style=flat-square)](LICENSE)
5
+ ![Runtime dependencies: none](https://img.shields.io/badge/dependencies-none-2ea44f?style=flat-square)
6
+
7
+ **One grid, one class per breakout.** Put your content in a `.breakouts` container: every child sits in a centred reading column, and any of them can widen to `popout`, `feature` or `full` with a single class. On a phone the same markup keeps the text off the screen edges, with no media query.
8
+
9
+ Around the grid sits a deliberately thin layer: colour tokens with light and dark modes, a reset, base typography, a few utilities and three themes. Use all of it, or only the grid.
10
+
11
+ **[Live demo →](https://cantilux.github.io/breakouts/)**
12
+
13
+ ![The breakouts grid: seven column tracks (full, feature, popout, content, popout, feature, full) and the span of .full, .feature, .popout, the default content column, .feature-start and .full-end](.github/assets/grid.svg)
14
+
15
+ > [!NOTE]
16
+ > Breakouts is in `0.x`. Until `1.0.0`, a minor version may still rename or remove a class, a custom property or a Sass variable; the [changelog](CHANGELOG.md) says which.
17
+
18
+ ## Contents
19
+
20
+ - [Installation](#installation)
21
+ - [The grid](#the-grid)
22
+ - [Base styles](#base-styles)
23
+ - [Colour tokens and modes](#colour-tokens-and-modes)
24
+ - [Utilities](#utilities)
25
+ - [Themes](#themes)
26
+ - [Customisation](#customisation)
27
+ - [Browser support](#browser-support)
28
+ - [Principles](#principles)
29
+ - [Workflow](#workflow)
30
+ - [Credits and licence](#credits-and-licence)
4
31
 
5
32
  ## Installation
6
33
 
7
- ```bash
34
+ ```sh
8
35
  npm install breakouts
36
+ # or: pnpm add breakouts · yarn add breakouts · bun add breakouts
9
37
  ```
10
38
 
11
- ```bash
12
- bun add breakouts
13
- ```
14
-
15
- ## Usage
39
+ ### With Sass
16
40
 
17
- ### SCSS
41
+ The Sass sources let you configure everything at build time. They use the module system (`@use`, `@forward`), so they need **Dart Sass**: LibSass and Ruby Sass cannot read them.
18
42
 
19
43
  ```scss
20
- @use 'breakouts' as *;
44
+ @use 'pkg:breakouts'; // tokens, reset, typography, grid, utilities
21
45
  ```
22
46
 
23
- This imports the full bundle: color tokens, reset, base typography, layout, and utilities.
47
+ The `pkg:` URL needs Sass's Node package importer (Dart Sass 1.71 or newer): `--pkg-importer=node` on the command line, or in JavaScript:
24
48
 
25
- For smaller Sass builds, import only the layer you need:
49
+ ```js
50
+ import * as sass from 'sass'
26
51
 
27
- ```scss
28
- @use 'breakouts/src/core';
29
- @use 'breakouts/src/utilities';
52
+ sass.compile('style.scss', { importers: [new sass.NodePackageImporter()] })
30
53
  ```
31
54
 
32
- ### CSS
55
+ Without it, add `node_modules` to the load paths (`--load-path=node_modules`) and drop the prefix: `@use 'breakouts';`.
56
+
57
+ ### Plain CSS
58
+
59
+ No Sass? The package ships the full bundle, built with the default settings:
33
60
 
34
61
  ```html
35
- <link rel="stylesheet" href="/node_modules/breakouts/dist/breakouts.css" />
62
+ <link rel="stylesheet" href="node_modules/breakouts/dist/breakouts.css">
63
+ <!-- or from a CDN, pinned to a minor version -->
64
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/breakouts@0.8/dist/breakouts.min.css">
36
65
  ```
37
66
 
38
- The distributed CSS file is the full bundle.
67
+ ```js
68
+ import 'breakouts/dist/breakouts.css' // bundlers; breakouts.min.css is the minified build
69
+ ```
39
70
 
40
- ## Sass Layers
71
+ ### Entry points
41
72
 
42
- | Import | Includes |
43
- | --- | --- |
44
- | `@use 'breakouts'` | Full bundle |
45
- | `@use 'breakouts/src/core'` | Color tokens and layout primitives |
46
- | `@use 'breakouts/src/base'` | Core, reset, and base typography |
47
- | `@use 'breakouts/src/reset'` | Reset only |
48
- | `@use 'breakouts/src/utilities'` | Color, spacing, position, and typography utilities |
49
- | `@use 'breakouts/src/base/layout'` | Layout module only |
73
+ Every entry point emits its layers in the same order, so you can stop at the size you need:
50
74
 
51
- ## Core Layout API
75
+ | `@use` | Emits | Use it when |
76
+ | --- | --- | --- |
77
+ | `'pkg:breakouts'` | Everything below, utilities last | You want the whole kit |
78
+ | `'pkg:breakouts/src/base'` | Colour tokens, reset, typography, grid | You want the defaults but write your own utilities |
79
+ | `'pkg:breakouts/src/core'` | Colour tokens and grid | You have your own reset and typography |
80
+ | `'pkg:breakouts/src/base/layout'` | The grid and its custom properties only | You want nothing but the layout |
81
+ | `'pkg:breakouts/src/reset'` | The reset only | |
82
+ | `'pkg:breakouts/src/utilities'` | Colour, spacing, position and typography utilities | Next to `core` or `base`: the colour utilities read the `--color-*` tokens |
52
83
 
53
- The main abstraction is the `.breakouts` container. Every direct child defaults to the centered content column, and modifier classes let individual elements break outward.
84
+ ### First page
54
85
 
55
86
  ```html
56
87
  <article class="breakouts">
57
- <p>Default content width</p>
58
- <figure class="popout">Slightly wider</figure>
59
- <pre class="feature">Wider still</pre>
60
- <aside class="feature-start">Anchored to the inline start side</aside>
61
- <img class="full" src="hero.jpg" alt="" />
88
+ <h1>Field notes</h1>
89
+ <p>Text stays in the reading column.</p>
90
+ <figure class="popout">A slightly wider figure</figure>
91
+ <pre class="feature">A wide code block</pre>
92
+ <img class="full" src="horizon.jpg" alt="The horizon at dusk">
93
+ <aside class="feature-start">A note pulled out to the start side</aside>
62
94
  </article>
63
95
  ```
64
96
 
65
- ### Layout classes
97
+ ## The grid
66
98
 
67
- | Class | Purpose |
68
- | --- | --- |
69
- | `.breakouts` | Main named-line breakout grid container |
70
- | `.content` | Explicitly place an item in the content column |
71
- | `.popout` | Slight breakout outside content |
72
- | `.popout-start`, `.popout-end` | One-sided popout breakout |
73
- | `.popout-left`, `.popout-right` | Physical aliases for one-sided popout breakout |
74
- | `.feature` | Larger breakout for emphasis |
75
- | `.feature-start`, `.feature-end` | One-sided feature breakout |
76
- | `.feature-left`, `.feature-right` | Physical aliases for one-sided feature breakout |
77
- | `.full` | Full-width breakout |
78
- | `.full-start`, `.full-end` | One-sided full breakout |
79
- | `.full-left`, `.full-right` | Physical aliases for one-sided full breakout |
80
- | `.container` | Traditional centered wrapper |
81
- | `.full-bleed` | Viewport-wide utility outside the named-grid pattern |
82
- | `.breakout` | Simpler wide utility outside the named-grid pattern |
83
-
84
- ### Nested breakout grids
85
-
86
- Any `.breakouts` or `.breakouts-grid` item can also be placed with a breakout class and then reuse the same track system for its own children.
99
+ `.breakouts` is a CSS grid with seven column tracks between named lines, after Ryan Mulligan's [Layout Breakouts with CSS Grid](https://ryanmulligan.dev/blog/layout-breakouts/):
87
100
 
88
- ```html
89
- <section class="breakouts">
90
- <div class="full breakouts">
91
- <p>Nested content column</p>
92
- <figure class="feature">Nested feature width</figure>
93
- <img class="full" src="wide.jpg" alt="" />
94
- </div>
95
- </section>
101
+ ```text
102
+ [full-start] --full [feature-start] --feature [popout-start] --popout
103
+ [content-start] --content [content-end]
104
+ --popout [popout-end] --feature [feature-end] --full [full-end]
96
105
  ```
97
106
 
98
- The nested grid inherits the parent track definition, so child placement classes keep the same meaning inside wider regions.
107
+ Every direct child goes into `content`. A placement class moves it out to a wider pair of lines:
108
+
109
+ | Class | Spans | |
110
+ | --- | --- | --- |
111
+ | *(none)*, `.content` | `content` | The reading column |
112
+ | `.popout` | `popout` | Content plus `--popout` on each side |
113
+ | `.feature` | `feature` | Popout plus `--feature` on each side |
114
+ | `.full` | `full` | Edge to edge of the container |
115
+ | `.popout-start`, `.feature-start`, `.full-start` | From that area's start line to `content-end` | One-sided: out on the start side only |
116
+ | `.popout-end`, `.feature-end`, `.full-end` | From `content-start` to that area's end line | One-sided: out on the end side only |
117
+ | `.popout-left`, `.feature-left`, `.full-left` | As `-start` | Physical aliases |
118
+ | `.popout-right`, `.feature-right`, `.full-right` | As `-end` | Physical aliases |
99
119
 
100
- ### Default sizing tokens
120
+ The placement classes work on direct children of a `.breakouts` (or `.breakouts-grid`, an alias kept for older markup). Rows are spaced by `row-gap: var(--gap)`, so headings and paragraphs that are direct children lose their block margins and the two do not add up; a spacing utility such as `my-4` still sets them.
101
121
 
102
- These CSS custom properties drive the layout and can be overridden per scope:
122
+ ### Sizes
123
+
124
+ Each track is a custom property on `:root`:
103
125
 
104
126
  ```css
105
127
  :root {
106
- --gap: clamp(1rem, 6vw, 3rem);
107
- --full: minmax(var(--gap), 1fr);
128
+ --gap: clamp(1rem, 6vw, 3rem); /* row gap, and the smallest outer gutter */
129
+ --full: minmax(var(--gap), 1fr); /* whatever is left, never less than --gap */
108
130
  --feature: minmax(0, 5rem);
109
131
  --popout: minmax(0, 2rem);
110
132
  --content: min(50ch, 100% - var(--gap) * 2);
111
133
  }
112
134
  ```
113
135
 
114
- ## Utility Layer
136
+ `popout` and `feature` can shrink to nothing, `full` never below `--gap`. As the container narrows, the wide areas give way first and the text never touches the edge. With the default `system-ui` font at 16px, measured in Chromium:
115
137
 
116
- Breakouts intentionally ships only a small utility surface around the breakout layout.
138
+ | Viewport | `--gap` | `content` | `.popout` | `.feature` | `.full` |
139
+ | --- | --- | --- | --- | --- | --- |
140
+ | 375px | 22.5px | 330px | 330px | 330px | 375px |
141
+ | 768px | 46px | 504px | 568px | 676px | 768px |
142
+ | 1440px | 48px | 504px | 568px | 728px | 1440px |
117
143
 
118
- ### Spacing
144
+ Below about 573px, `.popout` and `.feature` are as wide as the text; from about 824px every area has its full size. `50ch` is measured in the font of the `.breakouts` element, so another font or size moves these numbers.
119
145
 
120
- Spacing classes are generated from this scale:
146
+ To change the proportions, override the properties on any element, not only `:root`:
121
147
 
122
- ```scss
123
- $space-scale: (
124
- 0: 0,
125
- 1: 0.25rem,
126
- 2: 0.5rem,
127
- 3: 0.75rem,
128
- 4: 1rem,
129
- 5: 1.25rem,
130
- 6: 1.5rem,
131
- auto: auto
132
- );
148
+ ```css
149
+ .longform {
150
+ --content: min(65ch, 100% - var(--gap) * 2); /* keep the gutter term */
151
+ --feature: minmax(0, 8rem);
152
+ }
153
+ ```
154
+
155
+ ### Nesting
156
+
157
+ A `.breakouts` that is a direct child of another `.breakouts` becomes a [`subgrid`](https://developer.mozilla.org/docs/Web/CSS/CSS_grid_layout/Subgrid) of it: its columns are the parent's columns, so nested content lines up with the outer text.
158
+
159
+ ```html
160
+ <section class="breakouts">
161
+ <p>Outer text</p>
162
+ <div class="feature breakouts">
163
+ <p>Lines up with the outer text</p>
164
+ <figure class="popout">Lines up with an outer .popout</figure>
165
+ <img class="full" src="wide.jpg" alt=""> <!-- as wide as the nested .feature -->
166
+ </div>
167
+ </section>
133
168
  ```
134
169
 
135
- Available prefixes:
170
+ A class that reaches past the nested area fills that area: `.full` inside a nested `.feature` is as wide as the `.feature`. A browser without subgrid gives the nested grid its own tracks instead, sized to the nested area.
136
171
 
137
- - Margin: `m`, `mt`, `mr`, `mb`, `ml`, `mx`, `my`
138
- - Padding: `p`, `pt`, `pr`, `pb`, `pl`, `px`, `py`
172
+ ### Older helpers
139
173
 
140
- `auto` is generated for margin utilities only. Padding utilities are generated from the numeric spacing values.
174
+ These predate the grid. They keep working, and they are not developed further.
141
175
 
142
- ### Typography
176
+ | Class | What it does |
177
+ | --- | --- |
178
+ | `.container` | Centred wrapper: `max-width: 72rem`, `padding-inline: 1rem` |
179
+ | `.grid` | Three columns with the children in the middle one, `minmax(0, var(--container-max-width, 64rem))` wide, and `gap: var(--gap)` |
180
+ | `.grid--full` | On a `.grid`: the children span all three columns |
181
+ | `.full-bleed` | `100vw` wide, pulled out to the viewport's left edge; the parent must be centred in the viewport |
182
+ | `.full-bleed--left`, `.full-bleed--right` | From that viewport edge to the parent's opposite edge |
183
+ | `.breakout`, `.breakout--left`, `.breakout--right` | Deprecated aliases of `.full-bleed*`, removed in the next major version |
184
+
185
+ `100vw` includes a classic scrollbar, so a page using `.full-bleed` may need `overflow-x: clip` on `body`. Inside a `.breakouts`, prefer `.full`: it needs neither.
186
+
187
+ ## Base styles
143
188
 
144
- - Alignment: `text-left`, `text-center`, `text-right`, `text-justify`
145
- - Transform: `text-uppercase`, `text-lowercase`, `text-capitalize`
146
- - Weight/style: `font-bold`, `font-normal`, `italic`, `not-italic`
147
- - Leading: `leading-tight`, `leading-normal`, `leading-loose`
189
+ In the full bundle and in `src/base`, not in `src/core`.
148
190
 
149
- ### Colors
191
+ - **Reset.** `box-sizing: border-box` on every element and pseudo-element, and **every margin and padding set to 0**, lists included: give `ul` and `ol` a `padding-inline-start` if you want the bullets inside the box. `html` and `body` get `width: 100%`.
192
+ - **Body.** `system-ui, sans-serif`, `1rem`, `line-height: 1.6`, `var(--color-text)` on `var(--color-background)`.
193
+ - **Headings.** `font-weight: 600`, `line-height: 1.2`, `margin: 2rem 0 1rem`, `var(--color-text)`; sizes `h1` 2.25rem · `h2` 1.75rem · `h3` 1.5rem · `h4` 1.25rem · `h5` 1.125rem · `h6` 1rem.
194
+ - **Paragraphs** `margin: 1rem 0`. **Links** `var(--color-primary)`, underlined, `var(--color-secondary)` on hover.
150
195
 
151
- Base tokens exposed as CSS variables:
196
+ ## Colour tokens and modes
152
197
 
153
- - `--color-background`
154
- - `--color-surface`
155
- - `--color-text`
156
- - `--color-muted`
157
- - `--color-primary`
158
- - `--color-secondary`
159
- - `--color-accent`
160
- - `--color-success`
161
- - `--color-error`
162
- - `--color-warning`
198
+ Every colour is a custom property, so your own CSS can use them:
163
199
 
164
- Common utility classes:
200
+ | Token | Light | Dark |
201
+ | --- | --- | --- |
202
+ | `--color-background` | `#ffffff` | `#111111` |
203
+ | `--color-surface` | `#f0f0f0` | `#1a1a1a` |
204
+ | `--color-text` | `#111111` | `#eeeeee` |
205
+ | `--color-muted` | `#666666` | `#aaaaaa` |
206
+ | `--color-primary` | `#0055ff` | The light value, 50% lighter |
207
+ | `--color-secondary` | `#0033aa` | The light value, 50% lighter |
208
+ | `--color-accent` | `#ff4081` | The light value, 50% lighter |
209
+ | `--color-success` | `#28a745` | The light value, 50% lighter |
210
+ | `--color-error` | `#dc3545` | The light value, 50% lighter |
211
+ | `--color-warning` | `#ffc107` | The light value, 50% lighter |
212
+ | `--color-neutral-100` … `-900` | `#f8f9fa` … `#212529` | The same in every mode |
165
213
 
166
- - Background: `bg-background`, `bg-surface`, `bg-primary`, `bg-secondary`, `bg-accent`, `bg-muted`
167
- - Text: `text-background`, `text-color`, `text-primary`, `text-secondary`, `text-accent`, `text-muted`
214
+ "50% lighter" is Sass's `color.scale($lightness: 50%)`: brand and status colours stay readable on a dark background, and changing a light value changes its dark value too.
215
+
216
+ Which values apply:
217
+
218
+ | Markup | Mode |
219
+ | --- | --- |
220
+ | `<html>` | Follows the system: dark when `prefers-color-scheme: dark` |
221
+ | `<html class="dark">` | Always dark |
222
+ | `<html class="light">` | Always light, even on a dark system |
168
223
 
169
- ## Themes And Color Modes
224
+ Each mode also sets `color-scheme`, so form controls and scrollbars match.
170
225
 
171
- Breakouts supports explicit light/dark classes on `<html>` and also respects `prefers-color-scheme` when no explicit class is present.
226
+ `.dark` and `.light` work on any element, not only `<html>`: they redefine the tokens for that subtree. Text that inherits its colour from `body` keeps the page's colour, though, so give the element its own background and text colour:
172
227
 
173
228
  ```html
174
- <html class="dark">
229
+ <section class="dark bg-background text-color">…</section>
175
230
  ```
176
231
 
177
- Prebuilt themes:
232
+ ## Utilities
178
233
 
179
- | Theme | Import |
234
+ Optional, and always emitted last: on equal specificity a utility beats the grid and the base styles, so `class="container px-0"` removes the container's padding.
235
+
236
+ | Group | Classes |
180
237
  | --- | --- |
181
- | Chupa Pop | `@use 'breakouts/src/theme/chupa-pop';` |
182
- | Medical | `@use 'breakouts/src/theme/medical';` |
183
- | Tootsie Pop | `@use 'breakouts/src/theme/tootsie-pop';` |
238
+ | Background | `bg-background`, `bg-surface`, `bg-primary`, `bg-secondary`, `bg-accent`, `bg-muted`, `bg-success`, `bg-error`, `bg-warning` |
239
+ | Text colour | `text-color` (`--color-text`), `text-background`, `text-surface`, `text-primary`, `text-secondary`, `text-accent`, `text-muted`, `text-success`, `text-error`, `text-warning`, `text-neutral-100` … `text-neutral-900` |
240
+ | Margin | `m-*`, `mt-*`, `mr-*`, `mb-*`, `ml-*`, `mx-*`, `my-*` |
241
+ | Padding | `p-*`, `pt-*`, `pr-*`, `pb-*`, `pl-*`, `px-*`, `py-*` |
242
+ | Position | `position-static`, `position-relative`, `position-absolute`, `position-fixed`, `position-sticky` |
243
+ | Stacking | `z-index-0`, `z-index-1`, `z-index-10`, `z-index-100`, `z-index-1000` |
244
+ | Text align | `text-left`, `text-center`, `text-right`, `text-justify` |
245
+ | Text case | `text-uppercase`, `text-lowercase`, `text-capitalize` |
246
+ | Font | `font-bold`, `font-normal`, `italic`, `not-italic` |
247
+ | Line height | `leading-tight` (1.25), `leading-normal` (1.5), `leading-loose` (2) |
248
+
249
+ The `*` in the spacing classes is a step of `$space-scale`:
250
+
251
+ | Step | `0` | `1` | `2` | `3` | `4` | `5` | `6` | `auto` |
252
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- |
253
+ | Value | `0` | `0.25rem` | `0.5rem` | `0.75rem` | `1rem` | `1.25rem` | `1.5rem` | `auto`, margins only |
254
+
255
+ So `mx-auto` centres a block and `py-4` adds `1rem` above and below. The spacing utilities use physical sides (`margin-left`, `padding-top`): `ml-*` is the left side in every writing direction.
256
+
257
+ ## Themes
184
258
 
185
- If a theme overrides variables, load it before the main entrypoint:
259
+ A theme is a set of colours plus the modules that use them. Load it **before** the main entry point:
186
260
 
187
261
  ```scss
188
- @use 'breakouts/src/theme/chupa-pop';
189
- @use 'breakouts' as *;
262
+ @use 'pkg:breakouts/src/theme/chupa-pop';
263
+ @use 'pkg:breakouts';
190
264
  ```
191
265
 
192
- ## Customization
266
+ | Theme | Module | Colours |
267
+ | --- | --- | --- |
268
+ | Chupa Pop | `src/theme/chupa-pop` | Primary `#ecb838`, secondary `#ab4073`, accent `#196484` |
269
+ | Medical | `src/theme/medical` | Primary `#4682b4`, secondary `#9dc183`, accent `#a9a9a9`, muted `#6b7280`, background `#ffffff` |
270
+ | Tootsie Pop | `src/theme/tootsie-pop` | Primary `#93329e`, secondary `#f2711c`, accent `#e94057`, muted `#ffe5b4`; text `#fff5e6` on background `#4f2f4c`, dark in every mode |
193
271
 
194
- Override variables on first load with `@use ... with (...)`:
272
+ The second `@use` matters: on its own, a theme emits the tokens, reset, typography, grid, colour and typography utilities, but **not the spacing and position utilities**.
273
+
274
+ A theme takes its own variables: `$primary`, `$secondary` and `$accent`; Medical and Tootsie Pop also `$muted` and `$background`; Tootsie Pop also `$text`.
195
275
 
196
276
  ```scss
197
- @use 'breakouts' with (
198
- $color-primary: #d1ff4a,
199
- $color-accent: #8a2be2
200
- );
277
+ @use 'pkg:breakouts/src/theme/chupa-pop' with ($accent: #0f766e);
278
+ @use 'pkg:breakouts';
201
279
  ```
202
280
 
203
- Or import only the modules you need:
281
+ A theme has already configured the variables, so `@use 'pkg:breakouts' with (…)` after a theme is a compile error: configure the theme instead.
282
+
283
+ ## Customisation
284
+
285
+ ### At build time
286
+
287
+ Every variable below is `!default`, so `@use … with (…)` sets it on first load:
204
288
 
205
289
  ```scss
206
- @use 'breakouts/src/core';
207
- @use 'breakouts/src/base/layout';
208
- @use 'breakouts/src/base/spacing';
290
+ @use 'pkg:breakouts' with (
291
+ $color-primary: #7c3aed, // dark mode gets a lighter tint for free
292
+ $color-primary-dark: #c4b5fd, // or pick it yourself
293
+ $font-family-base: ('Inter', system-ui, sans-serif), // a list needs parentheses here
294
+ $space-scale: (0: 0, 1: 0.25rem, 2: 0.5rem, 4: 1rem, 8: 2rem, auto: auto),
295
+ );
296
+ ```
297
+
298
+ | Variables | Default |
299
+ | --- | --- |
300
+ | `$color-{name}` for `background`, `surface`, `text`, `muted`, `primary`, `secondary`, `accent`, `success`, `error`, `warning` | The light column of the [token table](#colour-tokens-and-modes) |
301
+ | `$color-{name}-dark` | The dark column |
302
+ | `$color-{name}-light` | `$color-{name}`: what `.light` applies |
303
+ | `$color-neutral-100` … `$color-neutral-900` | `#f8f9fa` … `#212529` |
304
+ | `$font-family-base`, `$font-size-base`, `$line-height-base` | `system-ui, sans-serif`, `1rem`, `1.6` |
305
+ | `$headings` | `(h1: 2.25rem, h2: 1.75rem, h3: 1.5rem, h4: 1.25rem, h5: 1.125rem, h6: 1rem)` |
306
+ | `$space-scale` | `(0: 0, 1: 0.25rem, 2: 0.5rem, 3: 0.75rem, 4: 1rem, 5: 1.25rem, 6: 1.5rem, auto: auto)` |
307
+
308
+ `$headings` and `$space-scale` **replace** the default map: list every level you want. `$headings: (h1: 3rem)` styles `h1` and leaves `h2`–`h6` without heading styles.
309
+
310
+ ### At runtime
311
+
312
+ The grid tracks and the colours are custom properties, so plain CSS changes them on any element, no Sass needed:
313
+
314
+ ```css
315
+ .longform { --content: min(60ch, 100% - var(--gap) * 2); }
316
+ ```
317
+
318
+ The colour modes set the tokens with more specific selectors than `:root` (up to `:root:not(.light):not(.dark)`), so a colour set on `:root` only reaches the default light mode. Load your rules after Breakouts and match each mode's selector:
319
+
320
+ ```css
321
+ :root, :root.light, .light { --color-primary: #7c3aed; }
322
+ :root.dark, .dark { --color-primary: #c4b5fd; }
323
+ @media (prefers-color-scheme: dark) {
324
+ :root:not(.light):not(.dark) { --color-primary: #c4b5fd; }
325
+ }
326
+ ```
327
+
328
+ For colours, the Sass variables are simpler: one value, and the build writes every mode.
329
+
330
+ ## Browser support
331
+
332
+ Every current browser. The grid relies on `min()`, `clamp()`, named grid lines and `:is()`; nesting relies on `subgrid` (Chrome and Edge 117, Firefox 71, Safari 16), and older browsers fall back to a nested grid with its own tracks.
333
+
334
+ ## Principles
335
+
336
+ - **The grid is the product.** Everything else is a thin, optional layer around it, and stays thin.
337
+ - **One class, one placement.** No column counts and no breakpoints in the markup: the tracks resize themselves.
338
+ - **ITCSS order.** Settings, reset, element styles, layout, utilities: every entry point emits its layers in that order, so a later layer wins on equal specificity.
339
+ - **Flat, low specificity.** Plain class selectors, no IDs, no `!important`. Your CSS wins without a fight.
340
+ - **Custom properties first.** Sass configures the defaults; custom properties change them per page or per section after the build.
341
+ - **Not utility-first.** A small set of utilities for the last adjustments, not a vocabulary to build pages with.
342
+
343
+ ## Workflow
344
+
345
+ ### Setup
346
+
347
+ ```sh
348
+ git clone https://github.com/Cantilux/breakouts.git
349
+ cd breakouts
350
+ npm install
209
351
  ```
210
352
 
211
- ## Demo
353
+ | Command | What it does |
354
+ | --- | --- |
355
+ | `npm run build` | Compiles `src/_index.scss` into `dist/breakouts.css` and `dist/breakouts.min.css`, then copies the readable build to `docs/breakouts.css` |
356
+ | `npm test` | Compiles every entry point and every theme, then imports the package the way a consumer would and checks the output |
357
+ | `npm run changeset` | Records a change for the next release |
358
+ | `npm run release:version` | Applies the pending changesets: new version and `CHANGELOG.md` entry (the release train runs it, not people) |
359
+
360
+ The demo is the static page `docs/index.html`: open it in a browser after `npm run build` and check the examples at a phone width and at a wide one.
361
+
362
+ ### Making a change
363
+
364
+ 1. **Branch from `master`** (from `hotfix` for an urgent fix, see [Releasing](#releasing)), named after the kind of change: `feat/…`, `fix/…`, `docs/…`. `master` is protected: changes reach it only through a pull request.
365
+ 2. **Commit with [Conventional Commits](https://www.conventionalcommits.org/)**: `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`, `ci:`, with a scope when it helps (`fix(layout):`).
366
+ 3. **Rebuild.** `dist/` and `docs/breakouts.css` are committed: run `npm run build` and commit them with the sources.
367
+ 4. **Add a changeset** with `npm run changeset` when the change affects what users get. The public API is the class names, the custom property names and the `!default` variable names:
368
+
369
+ | Bump | When |
370
+ | --- | --- |
371
+ | `patch` | Fixes that keep every class, custom property and variable working as before |
372
+ | `minor` | New classes, custom properties, variables or themes |
373
+ | `major` | A renamed or removed class, custom property or variable (while in `0.x`: `minor`) |
374
+
375
+ 5. **Open a pull request.** CI (`Test, build`) runs the tests and the build, fails if the committed `dist/` or `docs/breakouts.css` differ from a fresh build, and lists what npm would publish. Merging needs a green CI. If you change a class, a custom property or a variable, update this README and `docs/index.html` in the same pull request.
376
+
377
+ ### Releasing
378
+
379
+ Releases leave on a schedule, never from a laptop. Merge pull requests into `master` whenever they are ready, each with its changeset; nobody bumps versions by hand.
380
+
381
+ | Branch | Role |
382
+ | --- | --- |
383
+ | `master` | Work for the next scheduled release |
384
+ | `release` | What is on npm and on the demo site; only the release train (and admins) may push to it, and every push publishes |
385
+ | `hotfix` | Starts from `release`; urgent fixes merged here ship at once |
386
+
387
+ 1. **Every Thursday around 12:00 (Rome time)** the **Release train** workflow takes `master`. With no pending changesets it stops. Otherwise it runs `npm run release:version` (new version, `CHANGELOG.md` entry), the tests, the build and the package check, pushes the `chore: release x.y.z` commit to `master`, fast-forwards `release` to it and moves `hotfix` there too.
388
+ 2. **The push to `release`** starts the **Release** workflow: it sees a version npm does not have yet, runs the checks again, publishes with npm trusted publishing (with provenance) and tags `vX.Y.Z`. The same push starts **Docs**, which deploys the demo site from `release`, so it always matches what is on npm.
389
+ 3. **Hotfix:** open a pull request into `hotfix` with a `patch` changeset (CI rejects `minor` and `major` there) and merge it. The train releases `hotfix` right away the same way, then merges it back into `master`; if that merge conflicts, it opens a pull request to resolve, and the Thursday train waits for it. Never land the same fix in `master` too: it gets there through the merge back.
390
+ 4. **Out of schedule:** a maintainer can start the train by hand from Actions > Release train > Run workflow, choosing `master` or `hotfix`. It does exactly what the scheduled run does.
391
+
392
+ A pull request with no changeset ships nothing: with no pending changesets the train stops, so npm keeps the old version and the demo site, which deploys only from `release`, keeps the old page. Changes to the README or to `docs/` count: the README ships in the package, so they take a `patch` changeset like any fix.
393
+
394
+ The train refuses to release when `release` has commits its source branch lacks, and a branch that moved while it ran makes it stop; it is safe to run again. Its git pushes use the `RELEASE_DEPLOY_KEY` deploy key, the bypass actor of the rulesets on `master`, `hotfix` and `release` (GitHub does not let the workflow token bypass rulesets, and its pushes would not start **Release**). npm never sees the key: publishing uses trusted publishing.
395
+
396
+ Run `npm pack --dry-run` to see exactly what npm would receive.
397
+
398
+ ## Credits and licence
399
+
400
+ The named-line grid comes from Ryan Mulligan's article [Layout Breakouts with CSS Grid](https://ryanmulligan.dev/blog/layout-breakouts/) (2022). Breakouts packages it with one-sided variants, nesting and a small toolkit around it.
212
401
 
213
- GitHub Pages demo: [cantilux.github.io/breakouts](https://cantilux.github.io/breakouts)
402
+ Released under the [MIT licence](LICENSE), © 2025 Enrico Cantile.
package/_index.scss ADDED
@@ -0,0 +1,3 @@
1
+ // Lets `@use 'breakouts'` resolve through plain load paths (node_modules).
2
+
3
+ @forward 'src/breakouts';