@abraham/modern.css 0.0.0-stage → 0.0.0
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/LICENSE +21 -0
- package/README.md +236 -2
- package/dist/modern.css +2241 -0
- package/dist/modern.min.css +1 -0
- package/package.json +64 -4
- package/skills/modern-css/SKILL.md +101 -0
- package/skills/modern-css/assets/page.html +64 -0
- package/skills/modern-css/references/markup.md +219 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abraham Williams
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,237 @@
|
|
|
1
|
-
#
|
|
1
|
+
# modern.css
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A clean, modern classless CSS framework. Add one stylesheet to semantic HTML and get a polished page with no classes.
|
|
4
|
+
|
|
5
|
+
[Demo](https://modern.ink/)
|
|
6
|
+
|
|
7
|
+
- Styles plain HTML elements: typography, lists, tables, forms, buttons, `details`, `dialog`, popovers, progress, and more.
|
|
8
|
+
- Follows the system light/dark preference, with a manual override.
|
|
9
|
+
- Respects `prefers-reduced-motion`, with a manual override.
|
|
10
|
+
- Lays out sticky top bars, side navs, and rails from plain markup.
|
|
11
|
+
- Customize it with a handful of CSS custom properties.
|
|
12
|
+
- Everything sits in a cascade layer with zero specificity, so your own CSS always wins without `!important`.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
From a CDN:
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@abraham/modern.css/dist/modern.min.css" />
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or from npm, imported through a bundler that resolves package paths:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm install @abraham/modern.css
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```css
|
|
29
|
+
@import "@abraham/modern.css"; /* dist/modern.css */
|
|
30
|
+
@import "@abraham/modern.css/min"; /* dist/modern.min.css */
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
modern.css is meant to be the page's only stylesheet, and it owns the document layout.
|
|
34
|
+
|
|
35
|
+
## Use with an AI agent
|
|
36
|
+
|
|
37
|
+
The [`modern-css` agent skill](skills/modern-css/SKILL.md) teaches coding agents such as GitHub Copilot, Claude Code, and Codex how to add modern.css to a site. It covers converting class-based markup to semantic HTML, theming, and checking the result. Install it with the [GitHub CLI](https://cli.github.com/), which asks which agent to set it up for:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
gh skills add abraham/modern.css modern-css
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If you installed the package from npm, it's also in `node_modules/@abraham/modern.css/skills/modern-css` to copy into your agent's skills folder. Then ask your agent something like "Restyle this site with modern.css."
|
|
44
|
+
|
|
45
|
+
## Customize
|
|
46
|
+
|
|
47
|
+
Set any of these on `:root`. The color, spacing, corner, and code-font properties also work on any element, to scope them to that part of the page. `--m-font-sans`, `--m-font-size`, and `--m-measure` set the page's font, text size, and column width, so set them on `:root`. Try them live on the [Theming page](https://modern.ink/theming.html), and see whole pages themed with nothing else in the [showcase](https://modern.ink/showcase/).
|
|
48
|
+
|
|
49
|
+
| Property | Default | Purpose |
|
|
50
|
+
| ---------------- | ----------------------------------------- | ------------------------------------------------------------------------- |
|
|
51
|
+
| `--m-primary` | `oklch(55% 0.19 40)` (orange) | Accent for links, buttons, focus rings, checkboxes, sliders, and progress |
|
|
52
|
+
| `--m-on-primary` | `contrast-color()` of the primary | Text color on primary backgrounds; unset by default |
|
|
53
|
+
| `--m-density` | `1` | Spacing multiplier, such as `0.75` for compact or `1.25` for airy |
|
|
54
|
+
| `--m-radius` | `0.5rem` | Corner radius for controls and cards |
|
|
55
|
+
| `--m-font-sans` | `system-ui, sans-serif` | Body and UI font |
|
|
56
|
+
| `--m-font-mono` | `ui-monospace, SFMono-Regular, monospace` | Code font |
|
|
57
|
+
| `--m-font-size` | `1rem` | Base text size; the type scale derives from it |
|
|
58
|
+
| `--m-measure` | `100ch` | Width of the content column |
|
|
59
|
+
|
|
60
|
+
```css
|
|
61
|
+
:root {
|
|
62
|
+
--m-primary: oklch(60% 0.18 150);
|
|
63
|
+
--m-density: 0.875;
|
|
64
|
+
--m-radius: 0.25rem;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Hover, subtle, and dark-mode variants of the primary color are derived automatically.
|
|
69
|
+
|
|
70
|
+
## Attributes
|
|
71
|
+
|
|
72
|
+
| Attribute | Where | Effect |
|
|
73
|
+
| ---------------------------------- | --------------------- | ------------------------------------------------------------------------ |
|
|
74
|
+
| `data-theme="light"` or `"dark"` | `html` or any element | Forces a color scheme instead of following the system |
|
|
75
|
+
| `data-motion="reduce"` | `html` or any element | Turns animations off, like `prefers-reduced-motion` |
|
|
76
|
+
| `data-contrast="more"` | `html` or any element | Raises contrast, like `prefers-contrast: more` |
|
|
77
|
+
| `data-scroll-progress` | `html` | Shows a reading progress bar at the top of the window |
|
|
78
|
+
| `aria-busy="true"` | Any element | Shows an inline loading spinner; on a container, dims it under one |
|
|
79
|
+
| `aria-current="page"` | A link in a `nav` | Marks the current page |
|
|
80
|
+
| `role="switch"` or `switch` | A checkbox | Renders it as a toggle switch |
|
|
81
|
+
| `role="button"` | A link | Styles it as a button; `aria-disabled="true"` dims it |
|
|
82
|
+
| `role="group"` | A wrapper | Joins buttons, or a field with buttons and `span` text, into one control |
|
|
83
|
+
| `aria-pressed="true"` or `"false"` | A button | A toggle: outlined when off, tinted with a thick primary border when on |
|
|
84
|
+
| `aria-invalid="true"` or `"false"` | A form field | Red border and warning icon, or green border and check, plus helper text |
|
|
85
|
+
|
|
86
|
+
A `search` element joins its field and button like `role="group"`, whether they sit in a `form` or directly inside it, and `input type="search"` gets a magnifier icon:
|
|
87
|
+
|
|
88
|
+
```html
|
|
89
|
+
<search>
|
|
90
|
+
<form action="/search">
|
|
91
|
+
<input type="search" name="q" aria-label="Search" />
|
|
92
|
+
<button>Search</button>
|
|
93
|
+
</form>
|
|
94
|
+
</search>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
In a `role="group"` with a field, a `span` is a text box for a prefix or unit. Point the field at it with `aria-describedby`:
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<label for="site">Website</label>
|
|
101
|
+
<span role="group">
|
|
102
|
+
<span id="site-prefix">https://</span>
|
|
103
|
+
<input id="site" name="site" aria-describedby="site-prefix" />
|
|
104
|
+
</span>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
A popover that is a list, or holds one, is a menu. It opens below the button that toggles it, flips when there isn't room, and closes on Escape or a click outside:
|
|
108
|
+
|
|
109
|
+
```html
|
|
110
|
+
<button popovertarget="actions">Actions</button>
|
|
111
|
+
<menu id="actions" popover>
|
|
112
|
+
<li><a href="/edit">Edit</a></li>
|
|
113
|
+
<li><button>Duplicate</button></li>
|
|
114
|
+
</menu>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
A label whose text is a `span` right before its field is a float label: it sits inside the empty field and shrinks to the top once the field is focused or filled. The field needs a placeholder, even `placeholder=" "`:
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<label>
|
|
121
|
+
<span>Email</span>
|
|
122
|
+
<input type="email" name="email" placeholder="ada@example.com" />
|
|
123
|
+
</label>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
An element with `aria-hidden="true"` inside a link, such as an emoji or an icon `svg`, is a decorative icon: it isn't underlined, and a favicon or `svg` in it is sized to the text.
|
|
127
|
+
|
|
128
|
+
A visited link keeps its color, and its underline turns gray.
|
|
129
|
+
|
|
130
|
+
A `small` right after a form field is styled as helper text. Connect it with `aria-describedby` so screen readers announce it:
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<label>
|
|
134
|
+
Username
|
|
135
|
+
<input name="username" aria-invalid="true" aria-describedby="username-help" />
|
|
136
|
+
<small id="username-help">That username is already taken.</small>
|
|
137
|
+
</label>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
A `small` right after a `fieldset` is helper text for the whole group, and turns red when a control in the group is invalid. `readonly` fields look fixed, labels of disabled controls are muted, and the page behind a modal `dialog` doesn't scroll (Safari doesn't support this yet). Add `closedby="any"` to a `dialog` to close it on a click outside, where supported.
|
|
141
|
+
|
|
142
|
+
A `meter` fills green in its `optimum` range, amber when suboptimal, and red when even less good. `aria-busy="true"` on a container, such as a `section`, `article`, `form`, list, or `div`, dims its content under a centered spinner; also add `inert` so its controls can't be clicked or tabbed to while it loads.
|
|
143
|
+
|
|
144
|
+
## Layout
|
|
145
|
+
|
|
146
|
+
The markup picks the layout, as the pages in the [layouts gallery](https://modern.ink/layouts/) show:
|
|
147
|
+
|
|
148
|
+
```html
|
|
149
|
+
<body>
|
|
150
|
+
<header>
|
|
151
|
+
<nav><!-- Sticky top bar --></nav>
|
|
152
|
+
<nav><!-- Optional sticky subnav --></nav>
|
|
153
|
+
<h1>Title</h1>
|
|
154
|
+
</header>
|
|
155
|
+
<nav><!-- Side nav --></nav>
|
|
156
|
+
<main>…</main>
|
|
157
|
+
<aside><!-- Rail, such as "On this page" --></aside>
|
|
158
|
+
<footer>…</footer>
|
|
159
|
+
</body>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
- A `nav` directly inside `body > header` becomes a full-width bar that sticks for the whole page, while the rest of the header scrolls away. Its content spans the widest layout on every page, so its links stay put whether or not a page has side columns. To keep a header nav from sticking, nest it one level deeper.
|
|
163
|
+
- A second `nav` right after the top bar becomes a subnav that sticks below it. When the top bar slides away in Chromium, the subnav moves up into its place.
|
|
164
|
+
- A `nav` or `aside` directly inside `body` becomes a column beside `main` on wide screens. Both stick below the top bar, and a side nav taller than the window scrolls on its own. On narrow screens everything stacks, and navs become rows that scroll sideways.
|
|
165
|
+
- A `nav` with two lists puts them at opposite ends, such as a brand and links. In the top bar it stays one line.
|
|
166
|
+
- A `nav` whose list is an `ol` is a breadcrumb trail. Separators are drawn between items but not read aloud, and the last item is muted.
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<nav aria-label="Breadcrumb">
|
|
170
|
+
<ol>
|
|
171
|
+
<li><a href="/">Home</a></li>
|
|
172
|
+
<li><a href="/docs/">Docs</a></li>
|
|
173
|
+
<li><a href="/docs/layout/" aria-current="page">Layout</a></li>
|
|
174
|
+
</ol>
|
|
175
|
+
</nav>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- Anchor links, focused elements, and sticky table headers stop below the top bar.
|
|
179
|
+
- Once the page scrolls, the top bar casts a shadow, and so does a table header row that has stuck below it.
|
|
180
|
+
- Code blocks that scroll sideways shade each edge that has more content. Nav rows and wide tables in a `figure` fade those edges instead.
|
|
181
|
+
|
|
182
|
+
## Web components
|
|
183
|
+
|
|
184
|
+
Load modern.css in a component's shadow root to style what's inside it:
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
this.attachShadow({ mode: "open" }).innerHTML = `
|
|
188
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@abraham/modern.css/dist/modern.min.css" />
|
|
189
|
+
<button>Save</button>
|
|
190
|
+
`;
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The component follows the page it's on: the page's `--m-*` properties, `data-theme`, `data-contrast`, and `data-motion` reach inside, and so do the system's color scheme, contrast, and motion preferences. On a page that doesn't load modern.css, the component uses the defaults and paints its own surface, and the host gets body's font and text color. Set `--m-*` properties on the host element to theme one component.
|
|
194
|
+
|
|
195
|
+
## Browser support
|
|
196
|
+
|
|
197
|
+
The two most recent major versions of Chrome, Edge, Firefox, and Safari (desktop and iOS). A few enhancements only run where supported, and other browsers get the static version:
|
|
198
|
+
|
|
199
|
+
- Smooth `details` expand and collapse (`interpolate-size`)
|
|
200
|
+
- Cross-fades between pages (`@view-transition`)
|
|
201
|
+
- A styled `select` picker (`appearance: base-select`)
|
|
202
|
+
- The scroll progress bar (`animation-timeline: scroll()`)
|
|
203
|
+
- Shadows on the top bar and stuck table headers, and the fading edges of nav rows and wide tables (`animation-timeline`)
|
|
204
|
+
- Hiding the top bar while scrolling down (`scroll-state()` container queries)
|
|
205
|
+
|
|
206
|
+
Older browsers aren't supported, but text stays readable in them. Browsers without `@layer` ignore the stylesheet, those without `light-dark()` fall back to the browser's black on white, and those without `contrast-color()` get black or white button text from the primary's lightness. `npm run test:legacy` checks text contrast on every demo page in engines back to Chromium 101, Firefox 97, and Safari 15.4.
|
|
207
|
+
|
|
208
|
+
## Accessibility
|
|
209
|
+
|
|
210
|
+
Colors meet WCAG AA in both schemes, focus rings are always visible, and forced-colors mode redraws custom controls in system colors. When the system asks for more contrast (`prefers-contrast: more`), or an element has `data-contrast="more"`, text reaches WCAG AAA (7:1), borders and underlines turn solid, and focus rings grow to 3px. A few things are up to your markup:
|
|
211
|
+
|
|
212
|
+
- Give a horizontally scrolling `pre` a `tabindex="0"` so keyboard users can scroll it.
|
|
213
|
+
- Label navigation landmarks, for example `<nav aria-label="Docs">`, when a page has more than one.
|
|
214
|
+
|
|
215
|
+
## Development
|
|
216
|
+
|
|
217
|
+
Requires Node 26, which runs the TypeScript tooling directly.
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
npm install
|
|
221
|
+
npm start # builds, rebuilds on change, and serves the demo at http://localhost:8080/
|
|
222
|
+
npm run build # writes dist/modern.css and dist/modern.min.css, copies modern.css into demo/dist, and generates demo/kitchen-sink.html
|
|
223
|
+
npm run lint # Stylelint, including the no-classes rule
|
|
224
|
+
npm run typecheck
|
|
225
|
+
npm run format # Prettier
|
|
226
|
+
npm run size # gzip and Brotli sizes; fails if dist/modern.min.css is over 8.5 KB gzipped
|
|
227
|
+
npm run size -- --report # what each source file costs; add --save to keep a baseline to compare against
|
|
228
|
+
npm test # motion, layout, and accessibility tests in Chromium and WebKit
|
|
229
|
+
npm run test:visual # screenshot tests in the Playwright Docker image; add -- --update-snapshots to rewrite them
|
|
230
|
+
npm run test:legacy # text contrast on every demo page in old engines (Chromium 101 on), plus a Chromium 127 kitchen-sink screenshot; -- -g 1.45 runs one release
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Demo examples live in `demo/components/*.html`, one `figure[data-example]` each. The kitchen sink is generated from them, so edit the component pages, not `demo/kitchen-sink.html`.
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
[MIT](LICENSE)
|