breakouts 0.8.1 → 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.
Files changed (3) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +316 -134
  3. package/package.json +16 -6
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,220 +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 (parent must be centered) |
82
- | `.full-bleed--left`, `.full-bleed--right` | From one viewport edge to the parent's opposite edge |
83
- | `.breakout`, `.breakout--left`, `.breakout--right` | Deprecated aliases of the `.full-bleed` classes, removed in the next major |
84
-
85
- ### Nested breakout grids
86
-
87
- 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/):
88
100
 
89
- ```html
90
- <section class="breakouts">
91
- <div class="full breakouts">
92
- <p>Nested content column</p>
93
- <figure class="feature">Nested feature width</figure>
94
- <img class="full" src="wide.jpg" alt="" />
95
- </div>
96
- </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]
97
105
  ```
98
106
 
99
- The nested grid is a `subgrid` of its parent, so its columns line up exactly with the outer ones. A child class that reaches past the nested area fills that area: `.full` inside a nested `.feature` is as wide as the `.feature`. Browsers without subgrid support fall back to a nested grid with its own tracks.
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 |
100
119
 
101
- ### 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.
102
121
 
103
- 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`:
104
125
 
105
126
  ```css
106
127
  :root {
107
- --gap: clamp(1rem, 6vw, 3rem);
108
- --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 */
109
130
  --feature: minmax(0, 5rem);
110
131
  --popout: minmax(0, 2rem);
111
132
  --content: min(50ch, 100% - var(--gap) * 2);
112
133
  }
113
134
  ```
114
135
 
115
- ## 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:
116
137
 
117
- 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 |
118
143
 
119
- The CSS follows ITCSS order: settings and tokens, reset, base typography, layout, then
120
- utilities last. A utility always wins over a layout class of equal specificity, so
121
- `class="container px-0"` removes the container padding.
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.
122
145
 
123
- ### Spacing
146
+ To change the proportions, override the properties on any element, not only `:root`:
124
147
 
125
- Spacing classes are generated from this scale:
148
+ ```css
149
+ .longform {
150
+ --content: min(65ch, 100% - var(--gap) * 2); /* keep the gutter term */
151
+ --feature: minmax(0, 8rem);
152
+ }
153
+ ```
126
154
 
127
- ```scss
128
- $space-scale: (
129
- 0: 0,
130
- 1: 0.25rem,
131
- 2: 0.5rem,
132
- 3: 0.75rem,
133
- 4: 1rem,
134
- 5: 1.25rem,
135
- 6: 1.5rem,
136
- auto: auto
137
- );
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>
138
168
  ```
139
169
 
140
- 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.
141
171
 
142
- - Margin: `m`, `mt`, `mr`, `mb`, `ml`, `mx`, `my`
143
- - Padding: `p`, `pt`, `pr`, `pb`, `pl`, `px`, `py`
172
+ ### Older helpers
144
173
 
145
- `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.
146
175
 
147
- ### 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 |
148
184
 
149
- - Alignment: `text-left`, `text-center`, `text-right`, `text-justify`
150
- - Transform: `text-uppercase`, `text-lowercase`, `text-capitalize`
151
- - Weight/style: `font-bold`, `font-normal`, `italic`, `not-italic`
152
- - Leading: `leading-tight`, `leading-normal`, `leading-loose`
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.
153
186
 
154
- ### Colors
187
+ ## Base styles
155
188
 
156
- Base tokens exposed as CSS variables:
189
+ In the full bundle and in `src/base`, not in `src/core`.
157
190
 
158
- - `--color-background`
159
- - `--color-surface`
160
- - `--color-text`
161
- - `--color-muted`
162
- - `--color-primary`
163
- - `--color-secondary`
164
- - `--color-accent`
165
- - `--color-success`
166
- - `--color-error`
167
- - `--color-warning`
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.
168
195
 
169
- Common utility classes:
196
+ ## Colour tokens and modes
170
197
 
171
- - Background: `bg-background`, `bg-surface`, `bg-primary`, `bg-secondary`, `bg-accent`, `bg-muted`
172
- - Text: `text-background`, `text-color`, `text-primary`, `text-secondary`, `text-accent`, `text-muted`
198
+ Every colour is a custom property, so your own CSS can use them:
173
199
 
174
- ## Themes And Color Modes
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 |
175
213
 
176
- Breakouts supports explicit light/dark classes on `<html>` and also respects `prefers-color-scheme` when no explicit class is present.
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 |
223
+
224
+ Each mode also sets `color-scheme`, so form controls and scrollbars match.
225
+
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:
177
227
 
178
228
  ```html
179
- <html class="dark">
229
+ <section class="dark bg-background text-color">…</section>
180
230
  ```
181
231
 
182
- Prebuilt themes:
232
+ ## Utilities
183
233
 
184
- | 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 |
185
237
  | --- | --- |
186
- | Chupa Pop | `@use 'breakouts/src/theme/chupa-pop';` |
187
- | Medical | `@use 'breakouts/src/theme/medical';` |
188
- | 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
189
258
 
190
- 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:
191
260
 
192
261
  ```scss
193
- @use 'breakouts/src/theme/chupa-pop';
194
- @use 'breakouts' as *;
262
+ @use 'pkg:breakouts/src/theme/chupa-pop';
263
+ @use 'pkg:breakouts';
195
264
  ```
196
265
 
197
- ## 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 |
198
271
 
199
- 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`.
200
275
 
201
276
  ```scss
202
- @use 'breakouts' with (
203
- $color-primary: #d1ff4a,
204
- $color-accent: #8a2be2
205
- );
277
+ @use 'pkg:breakouts/src/theme/chupa-pop' with ($accent: #0f766e);
278
+ @use 'pkg:breakouts';
206
279
  ```
207
280
 
208
- Every color has a `-dark` variant (used in dark mode) and a `-light` variant (used with `<html class="light">`). `-light` defaults to the base color; for brand and status colors `-dark` defaults to a lighter tint of the base color, so overriding `$color-primary` updates all three modes. Override a variant directly to pick it yourself, e.g. `$color-primary-dark: #9ab8ff`.
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
209
284
 
210
- Or import only the modules you need:
285
+ ### At build time
286
+
287
+ Every variable below is `!default`, so `@use … with (…)` sets it on first load:
211
288
 
212
289
  ```scss
213
- @use 'breakouts/src/core';
214
- @use 'breakouts/src/base/layout';
215
- @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); }
216
316
  ```
217
317
 
218
- ## Demo
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
351
+ ```
352
+
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.
219
401
 
220
- GitHub Pages demo: [cantilux.github.io/breakouts](https://cantilux.github.io/breakouts)
402
+ Released under the [MIT licence](LICENSE), © 2025 Enrico Cantile.
package/package.json CHANGED
@@ -1,24 +1,34 @@
1
1
  {
2
2
  "name": "breakouts",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "A focused SCSS/CSS breakout layout toolkit inspired by the Mulligan named-grid pattern.",
5
+ "homepage": "https://cantilux.github.io/breakouts/",
6
+ "bugs": "https://github.com/Cantilux/breakouts/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Cantilux/breakouts.git"
10
+ },
5
11
  "main": "dist/breakouts.css",
12
+ "sass": "src/breakouts.scss",
6
13
  "files": [
7
14
  "_index.scss",
8
15
  "src/",
9
- "dist/"
16
+ "dist/",
17
+ "CHANGELOG.md"
10
18
  ],
11
19
  "scripts": {
12
20
  "test": "sh test/compile.sh",
13
21
  "build": "sass src/_index.scss dist/breakouts.css --style=expanded && sass src/_index.scss dist/breakouts.min.css --style=compressed && cp dist/breakouts.css docs/breakouts.css",
14
- "release:patch": "(npm whoami || npm login --auth-type=web) && npm run build && npm version patch --sign-git-tag && npm publish --access public && git push --follow-tags",
15
- "release:minor": "(npm whoami || npm login --auth-type=web) && npm run build && npm version minor --sign-git-tag && npm publish --access public && git push --follow-tags",
16
- "release:major": "(npm whoami || npm login --auth-type=web) && npm run build && npm version major --sign-git-tag && npm publish --access public && git push --follow-tags"
22
+ "changeset": "changeset",
23
+ "release:version": "changeset version"
17
24
  },
18
25
  "author": "Cantilux",
19
26
  "license": "MIT",
20
27
  "devDependencies": {
28
+ "@changesets/cli": "3.0.3",
21
29
  "sass": "^1.86.2"
22
30
  },
23
- "sass": "src/breakouts.scss"
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
24
34
  }