ply-css 1.6.0 → 1.7.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/CLAUDE.md +18 -0
- package/PLY.md +38 -1
- package/README.md +50 -1
- package/bin/ply-purge.js +172 -0
- package/dist/css/ply-core.css +60 -77
- package/dist/css/ply-core.min.css +1 -1
- package/dist/css/ply-essentials.min.css +1 -1
- package/dist/css/ply-helpers.min.css +1 -1
- package/dist/css/ply.css +76 -93
- package/dist/css/ply.min.css +1 -1
- package/dist/css/ply.purged.min.css +1 -0
- package/dist/css/styles.css +76 -93
- package/dist/css/styles.min.css +1 -1
- package/llms-full.txt +40 -7
- package/llms.txt +1 -1
- package/package.json +18 -1
- package/ply-classes.json +25 -13
- package/purge.js +81 -0
- package/safelist.js +43 -0
- package/snippets/custom-theme.html +6 -3
- package/snippets/starter-page.html +2 -2
- package/src/scss/components/_buttons.scss +4 -4
- package/src/scss/components/_colors.scss +23 -35
- package/src/scss/components/_dialog-patterns.scss +1 -1
- package/src/scss/components/_forms.scss +15 -16
- package/src/scss/components/_helpers-core.scss +4 -4
- package/src/scss/components/_helpers.scss +3 -3
- package/src/scss/components/_livesearch.scss +6 -6
- package/src/scss/components/_multi-step-form.scss +5 -5
- package/src/scss/components/_navigation.scss +2 -2
- package/src/scss/components/_notifications.scss +2 -2
- package/src/scss/components/_reset.scss +7 -7
- package/src/scss/components/_rtl.scss +4 -4
- package/src/scss/components/_tables.scss +2 -1
- package/src/scss/components/_typography.scss +1 -1
package/CLAUDE.md
CHANGED
|
@@ -44,6 +44,7 @@ Create a custom theme by defining a `data-theme` value and overriding `--ply-*`
|
|
|
44
44
|
--ply-color-body: #1a1a1a;
|
|
45
45
|
--ply-color-headings: #78350f;
|
|
46
46
|
--ply-border-color: #fbbf24;
|
|
47
|
+
--ply-border-radius: 0.375rem;
|
|
47
48
|
--ply-color-accent: #b45309;
|
|
48
49
|
--ply-btn-default-bg: #b45309; /* Controls btn-primary + links */
|
|
49
50
|
--ply-btn-default-bg-hover: #92400e;
|
|
@@ -166,6 +167,20 @@ For quick demos — gives you ply's classes and dark mode, but no Sass variables
|
|
|
166
167
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ply-css@1/dist/css/ply.min.css">
|
|
167
168
|
```
|
|
168
169
|
|
|
170
|
+
## Tree-Shaking
|
|
171
|
+
|
|
172
|
+
For production builds, purge unused ply classes with the built-in `ply-purge` CLI or PostCSS plugin. Typical result: ~5KB gzipped per page.
|
|
173
|
+
|
|
174
|
+
- **PostCSS plugin**: `require('ply-css/purge')` — recommended for build pipelines
|
|
175
|
+
- **CLI**: `npx ply-purge --css <file> --content '<glob>' -o <output>`
|
|
176
|
+
- Auto-safelists dynamically-toggled classes (`active`, `sort-asc`, responsive grid variants)
|
|
177
|
+
|
|
178
|
+
See PLY.md "Tree-Shaking" section for full usage examples.
|
|
179
|
+
|
|
180
|
+
## Semantic Color Tokens
|
|
181
|
+
|
|
182
|
+
Use `--ply-color-error` and `--ply-color-success` for error/success states instead of hardcoding red/green. These tokens are used by `.input-error`, `.input-success`, `.error`, `.success`, `.required`, and multi-step form states. They're themeable via custom `data-theme` overrides.
|
|
183
|
+
|
|
169
184
|
## File Structure
|
|
170
185
|
|
|
171
186
|
- `src/scss/` — SCSS source (modern `@use`/`@forward` modules). **Use this when the project has a build step.**
|
|
@@ -173,6 +188,9 @@ For quick demos — gives you ply's classes and dark mode, but no Sass variables
|
|
|
173
188
|
- `components/_variables.scss` — Spacing, font sizes, breakpoints, border radius
|
|
174
189
|
- `components/_mixins.scss` — Button generator, clearfix, gradients, arrows, animations
|
|
175
190
|
- `dist/css/` — Compiled CSS bundles (for CDN or direct linking)
|
|
191
|
+
- `bin/ply-purge.js` — Standalone CLI for tree-shaking unused CSS
|
|
192
|
+
- `purge.js` — PostCSS plugin for tree-shaking in build pipelines
|
|
193
|
+
- `safelist.js` — Shared safelist for dynamically-toggled classes
|
|
176
194
|
- `PLY.md` — Complete AI instruction file with class reference
|
|
177
195
|
- `ply-classes.json` — Machine-readable class reference
|
|
178
196
|
- `snippets/` — Copy-paste HTML examples
|
package/PLY.md
CHANGED
|
@@ -27,6 +27,7 @@ Create a custom theme by defining a `data-theme` value and overriding `--ply-*`
|
|
|
27
27
|
--ply-color-body: #1a1a1a;
|
|
28
28
|
--ply-color-headings: #78350f;
|
|
29
29
|
--ply-border-color: #fbbf24;
|
|
30
|
+
--ply-border-radius: 0.375rem;
|
|
30
31
|
--ply-color-accent: #b45309;
|
|
31
32
|
--ply-btn-default-bg: #b45309;
|
|
32
33
|
--ply-btn-default-bg-hover: #92400e;
|
|
@@ -355,7 +356,7 @@ See `snippets/responsive-header.html` for a full working example.
|
|
|
355
356
|
- **`border-top`**, **`border-right`**, **`border-bottom`**, **`border-left`** — Single-side borders.
|
|
356
357
|
- **`border-thick`** — 3px solid border (all sides). Also `border-top-thick`, `border-right-thick`, `border-bottom-thick`, `border-left-thick`.
|
|
357
358
|
- **`no-border`** — Remove all borders. Also `no-border-top`, `no-border-right`, `no-border-bottom`, `no-border-left`.
|
|
358
|
-
- **`border-radius`** —
|
|
359
|
+
- **`border-radius`** — Uses `var(--ply-border-radius)` (default 0.25rem, themeable). `border-radius-lg`, `border-radius-xl`, `circle` (100%).
|
|
359
360
|
|
|
360
361
|
### Other Common Patterns
|
|
361
362
|
|
|
@@ -728,3 +729,39 @@ Ready-to-use HTML examples are in the `snippets/` directory:
|
|
|
728
729
|
| `ply-core.min.css` | Grid, buttons, forms, nav, alerts, tables, typography, essential helpers | ~17KB |
|
|
729
730
|
| `ply-essentials.min.css` | Grid, helpers, alignments, blocks only | ~7KB |
|
|
730
731
|
| `ply-helpers.min.css` | Helper utilities only | ~5KB |
|
|
732
|
+
|
|
733
|
+
## Tree-Shaking (Purge Unused CSS)
|
|
734
|
+
|
|
735
|
+
For production builds, purge unused ply classes to get bundle sizes comparable
|
|
736
|
+
to Tailwind's JIT output (~5KB gzipped for a typical page).
|
|
737
|
+
|
|
738
|
+
### PostCSS Plugin (recommended for build pipelines)
|
|
739
|
+
|
|
740
|
+
```sh
|
|
741
|
+
npm install -D @fullhuman/postcss-purgecss
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
```js
|
|
745
|
+
// postcss.config.js
|
|
746
|
+
const plyPurge = require('ply-css/purge');
|
|
747
|
+
|
|
748
|
+
module.exports = {
|
|
749
|
+
plugins: [
|
|
750
|
+
plyPurge({ content: ['./src/**/*.{html,jsx,tsx,vue}'] }),
|
|
751
|
+
],
|
|
752
|
+
};
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### CLI (standalone or CI)
|
|
756
|
+
|
|
757
|
+
```sh
|
|
758
|
+
npm install -D purgecss
|
|
759
|
+
npx ply-purge --css node_modules/ply-css/dist/css/ply.min.css \
|
|
760
|
+
--content 'src/**/*.{html,jsx,tsx}' \
|
|
761
|
+
-o public/ply.css
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
The purge tool auto-safelists dynamically-toggled classes (`active`,
|
|
765
|
+
`sort-asc`, responsive grid variants) so they aren't incorrectly removed.
|
|
766
|
+
Extend the safelist with `--safelist <class>` (CLI) or `safelist` option
|
|
767
|
+
(PostCSS plugin).
|
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ CSS frameworks were designed for humans reading documentation. But increasingly,
|
|
|
37
37
|
- **Start semantic** — ply automatically styles `<nav>`, `<table>`, `<code>`, `<blockquote>`, `<details>`, `<dialog>`, and more. Start with what HTML gives you, then reach for classes when you need them.
|
|
38
38
|
- **AI-native** — ships with `PLY.md` (AI instruction file) and `ply-classes.json` (machine-readable class reference). Class names are predictable: `.alert-blue`, `.btn-sm`, `.unit-50`.
|
|
39
39
|
- **Accessible by default** — `:focus-visible` outlines on all interactive elements (including `<summary>` and legacy components), `prefers-reduced-motion`, `prefers-color-scheme` dark mode, semantic HTML styling, WCAG AA contrast in both light and dark themes. Published [VPAT 2.5](https://plycss.com/docs/vpat) documenting conformance against all WCAG 2.1 Level A and AA criteria.
|
|
40
|
-
- **Small footprint** — ~21KB gzipped (full), ~17KB (core). No JavaScript runtime, no build step
|
|
40
|
+
- **Small footprint** — ~21KB gzipped (full), ~17KB (core), ~5KB with tree-shaking. No JavaScript runtime, no build step required.
|
|
41
41
|
- **Ratio-based grid** — think in percentages, not arbitrary columns. `unit-50` is 50%, `unit-33` is 33%. Responsive prefixes: `tablet-unit-*`, `phone-unit-*`.
|
|
42
42
|
- **Custom theming** — override `--ply-*` CSS custom properties to create any theme. Light and dark modes built in.
|
|
43
43
|
|
|
@@ -99,6 +99,7 @@ Override `--ply-*` CSS custom properties to create any theme:
|
|
|
99
99
|
--ply-color-accent: #92400e; /* Icons, badges, section accents */
|
|
100
100
|
--ply-btn-default-bg: #92400e; /* Primary button + links */
|
|
101
101
|
--ply-btn-secondary-bg: #78350f; /* Secondary button */
|
|
102
|
+
--ply-border-radius: 0.375rem; /* Global border radius */
|
|
102
103
|
--ply-btn-border-radius: 0.5rem; /* Button corner radius */
|
|
103
104
|
--ply-font-body: Palatino, Georgia, serif;
|
|
104
105
|
--ply-font-heading: Palatino, Georgia, serif;
|
|
@@ -131,6 +132,54 @@ For AI agents (Claude, Cursor, Copilot, Replit AI):
|
|
|
131
132
|
|
|
132
133
|
ply is standalone — it should not be used alongside Tailwind, Bootstrap, or other CSS frameworks.
|
|
133
134
|
|
|
135
|
+
## Tree-Shaking (Purge Unused CSS)
|
|
136
|
+
|
|
137
|
+
ply ships all 457 classes in every bundle. For production, you can purge unused
|
|
138
|
+
classes to get Tailwind-level bundle sizes (~5KB gzipped for a typical page).
|
|
139
|
+
|
|
140
|
+
### PostCSS Plugin
|
|
141
|
+
|
|
142
|
+
The recommended approach for projects with an existing PostCSS pipeline:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
npm install -D @fullhuman/postcss-purgecss
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
// postcss.config.js
|
|
150
|
+
const plyPurge = require('ply-css/purge');
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
plugins: [
|
|
154
|
+
plyPurge({ content: ['./src/**/*.{html,jsx,tsx,vue}'] }),
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### CLI
|
|
160
|
+
|
|
161
|
+
For standalone use or CI pipelines:
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
npm install -D purgecss
|
|
165
|
+
npx ply-purge --css node_modules/ply-css/dist/css/ply.min.css \
|
|
166
|
+
--content 'src/**/*.{html,jsx,tsx}' \
|
|
167
|
+
-o public/ply.css
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Results
|
|
171
|
+
|
|
172
|
+
| Scenario | Before | After (gzipped) | Reduction |
|
|
173
|
+
|----------|--------|-----------------|-----------|
|
|
174
|
+
| Single page (card) | 21 KB | ~5 KB | ~75% |
|
|
175
|
+
| All snippets (13 pages) | 21 KB | ~11 KB | ~48% |
|
|
176
|
+
| Real-world app page | 21 KB | ~5.5 KB | ~74% |
|
|
177
|
+
|
|
178
|
+
The purge tool auto-safelists dynamically-toggled classes (`active`,
|
|
179
|
+
`sort-asc`, etc.) and responsive grid variants so they aren't incorrectly
|
|
180
|
+
removed. Pass additional safelisted classes with `--safelist` (CLI) or the
|
|
181
|
+
`safelist` option (PostCSS).
|
|
182
|
+
|
|
134
183
|
## Development
|
|
135
184
|
|
|
136
185
|
```sh
|
package/bin/ply-purge.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ply-purge — Standalone CLI to tree-shake unused ply CSS.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx ply-purge --css dist/css/ply.min.css --content 'src/**\/*.html' -o dist/css/ply.purged.css
|
|
8
|
+
* npx ply-purge --css node_modules/ply-css/dist/css/ply.min.css --content 'app/**\/*.tsx' --content 'components/**\/*.tsx'
|
|
9
|
+
*
|
|
10
|
+
* Options:
|
|
11
|
+
* --css <file> Input CSS file (required)
|
|
12
|
+
* --content <glob> Content glob to scan for used classes (repeatable, required)
|
|
13
|
+
* -o, --output <file> Output file (defaults to <input>.purged.css)
|
|
14
|
+
* --safelist <class> Additional class names to keep (repeatable)
|
|
15
|
+
* --json Print stats as JSON instead of human-readable
|
|
16
|
+
* -h, --help Show help
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
"use strict";
|
|
20
|
+
|
|
21
|
+
const fs = require("fs");
|
|
22
|
+
const path = require("path");
|
|
23
|
+
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
|
|
26
|
+
if (args.includes("-h") || args.includes("--help") || args.length === 0) {
|
|
27
|
+
console.log(`
|
|
28
|
+
ply-purge — Tree-shake unused ply CSS
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
ply-purge --css <file> --content <glob> [-o <output>]
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
--css <file> Input CSS file to purge
|
|
35
|
+
--content <glob> Glob pattern for content files (repeatable)
|
|
36
|
+
-o, --output <file> Output file (default: <name>.purged.css)
|
|
37
|
+
--safelist <class> Extra class to preserve (repeatable)
|
|
38
|
+
--json Output stats as JSON
|
|
39
|
+
-h, --help Show this help
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
ply-purge --css node_modules/ply-css/dist/css/ply.min.css \\
|
|
43
|
+
--content 'src/**/*.{html,jsx,tsx}' \\
|
|
44
|
+
-o public/ply.css
|
|
45
|
+
|
|
46
|
+
ply-purge --css dist/css/ply.min.css \\
|
|
47
|
+
--content 'app/**/*.tsx' --content 'components/**/*.tsx'
|
|
48
|
+
`.trim());
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseArgs(argv) {
|
|
53
|
+
const result = { content: [], safelist: [], json: false };
|
|
54
|
+
let i = 0;
|
|
55
|
+
while (i < argv.length) {
|
|
56
|
+
const arg = argv[i];
|
|
57
|
+
if (arg === "--css" && argv[i + 1]) {
|
|
58
|
+
result.css = argv[++i];
|
|
59
|
+
} else if (arg === "--content" && argv[i + 1]) {
|
|
60
|
+
result.content.push(argv[++i]);
|
|
61
|
+
} else if ((arg === "-o" || arg === "--output") && argv[i + 1]) {
|
|
62
|
+
result.output = argv[++i];
|
|
63
|
+
} else if (arg === "--safelist" && argv[i + 1]) {
|
|
64
|
+
result.safelist.push(argv[++i]);
|
|
65
|
+
} else if (arg === "--json") {
|
|
66
|
+
result.json = true;
|
|
67
|
+
}
|
|
68
|
+
i++;
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function main() {
|
|
74
|
+
const opts = parseArgs(args);
|
|
75
|
+
|
|
76
|
+
if (!opts.css) {
|
|
77
|
+
console.error("Error: --css is required");
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
if (opts.content.length === 0) {
|
|
81
|
+
console.error("Error: at least one --content glob is required");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let PurgeCSS;
|
|
86
|
+
try {
|
|
87
|
+
PurgeCSS = require("purgecss").PurgeCSS;
|
|
88
|
+
} catch {
|
|
89
|
+
console.error(
|
|
90
|
+
"ply-purge requires purgecss.\n" +
|
|
91
|
+
"Install it: npm install -D purgecss"
|
|
92
|
+
);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const { safelistClasses, safelistPatterns } = require("../safelist");
|
|
97
|
+
|
|
98
|
+
const cssPath = path.resolve(opts.css);
|
|
99
|
+
if (!fs.existsSync(cssPath)) {
|
|
100
|
+
console.error(`Error: CSS file not found: ${cssPath}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const rawCss = fs.readFileSync(cssPath, "utf8");
|
|
105
|
+
const originalSize = Buffer.byteLength(rawCss, "utf8");
|
|
106
|
+
|
|
107
|
+
const purger = new PurgeCSS();
|
|
108
|
+
const results = await purger.purge({
|
|
109
|
+
content: opts.content,
|
|
110
|
+
css: [{ raw: rawCss }],
|
|
111
|
+
safelist: {
|
|
112
|
+
standard: [...safelistClasses, ...opts.safelist],
|
|
113
|
+
deep: safelistPatterns,
|
|
114
|
+
greedy: [/^:root$/, /^html$/, /^body$/],
|
|
115
|
+
},
|
|
116
|
+
defaultExtractor: (content) => {
|
|
117
|
+
const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [];
|
|
118
|
+
const innerMatches = content.match(/[^<>"'`\s.(){}[\]#,;]+/g) || [];
|
|
119
|
+
return [...new Set([...broadMatches, ...innerMatches])];
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (results.length === 0) {
|
|
124
|
+
console.error("Error: PurgeCSS returned no results");
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const purgedCss = results[0].css;
|
|
129
|
+
const purgedSize = Buffer.byteLength(purgedCss, "utf8");
|
|
130
|
+
|
|
131
|
+
const outputPath = opts.output
|
|
132
|
+
? path.resolve(opts.output)
|
|
133
|
+
: cssPath.replace(/(\.\w+)$/, ".purged$1");
|
|
134
|
+
|
|
135
|
+
const outputDir = path.dirname(outputPath);
|
|
136
|
+
if (!fs.existsSync(outputDir)) {
|
|
137
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fs.writeFileSync(outputPath, purgedCss, "utf8");
|
|
141
|
+
|
|
142
|
+
const reduction = (((originalSize - purgedSize) / originalSize) * 100).toFixed(1);
|
|
143
|
+
|
|
144
|
+
if (opts.json) {
|
|
145
|
+
console.log(
|
|
146
|
+
JSON.stringify({
|
|
147
|
+
input: opts.css,
|
|
148
|
+
output: path.relative(process.cwd(), outputPath),
|
|
149
|
+
originalBytes: originalSize,
|
|
150
|
+
purgedBytes: purgedSize,
|
|
151
|
+
reductionPercent: parseFloat(reduction),
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
} else {
|
|
155
|
+
console.log(`ply-purge complete`);
|
|
156
|
+
console.log(` Input: ${opts.css} (${formatBytes(originalSize)})`);
|
|
157
|
+
console.log(
|
|
158
|
+
` Output: ${path.relative(process.cwd(), outputPath)} (${formatBytes(purgedSize)})`
|
|
159
|
+
);
|
|
160
|
+
console.log(` Reduction: ${reduction}% removed`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function formatBytes(bytes) {
|
|
165
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
166
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
main().catch((err) => {
|
|
170
|
+
console.error(err.message || err);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
});
|