ui8px is a framework-agnostic CLI for keeping utility-class projects aligned to a strict 8px design grid. It is intended for Tailwind-style workflows where classes remain explicit in source code, while a policy layer prevents drift, raw palette usage, accidental 4px layout spacing, and repeated unreviewed patterns.
Run without installing:
npx ui8px@latest lint ./...For a full onboarding path, read the docs:
- 101: 8px Grid Design
- Why Tailwind
- CLI API Reference
- Policy Files
- Examples And Use Cases
- Testing, CI, And Publishing
npx ui8px init
npx ui8px init --preset go
npx ui8px lint ./...
npx ui8px lint ./... --ignore .manual .project
npx ui8px lint ./... --learn
npx ui8px validate aria ./...
npx ui8px validate grid --input class-map.json --output class-map.backlog.json
npx ui8px validate patterns ./...
npx ui8px policy reviewLegacy class-map validation is still supported:
npx ui8px --design grid --input class-map.json --output class-map.backlog.jsonui8px init creates:
.ui8px/
policy/
allowed.json
denied.json
scopes.json
groups.json
patterns.json
telemetry/
reports/
If .ui8px is missing, ui8px lint uses bundled defaults. For Go component libraries, use npx ui8px init --preset go to make ui/**, components/**, and utils/**/*.go control scope while examples and views remain strict layout scope.
ui8px reads the current working directory's .gitignore automatically during file scanning, including negation lines (!pattern) that re-include a previously ignored path. Use --ignore to add extra ignored files or folders for a single run:
npx ui8px lint ./... --ignore .manual .project snapshots
npx ui8px validate patterns ./... --ignore fixtures/reference
npx ui8px validate aria ./... --ignore .manualPlace scan paths before --ignore; every non-option value after --ignore is treated as an ignore entry.
Layout scope is strict 8px:
{
"spacing": {
"layout": ["0", "2", "4", "6", "8", "10", "12", "16", "20", "24"],
"control": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "10", "12"]
}
}Examples:
px-2= 8px, allowed in layout.px-4= 16px, allowed in layout.px-3= 12px, denied in layout as fine tuning.px-3is allowed in control scope for buttons, fields, badges, tabs, and other compact primitives.
Scopes are file/folder based:
{
"defaultScope": "layout",
"scopes": [
{
"name": "controls",
"files": ["src/ui/**", "src/components/**", "ui/**", "components/**"],
"spacing": "control"
},
{
"name": "layout",
"files": ["src/views/**", "src/pages/**", "examples/**", "internal/site/views/**"],
"spacing": "layout"
}
]
}npx ui8px lint ./...
npx ui8px lint ./... --ignore .manual .projectSupported source types:
.templand.html: staticclass="..."values..templfiles also go through the Go extractor, soClass: "...",cn(...)/Cn(...), andtempl.Attributes{"class": "..."}are recognised..css:@apply ...;utility lists..go: statictempl.Attributes{"class": "..."}style values, struct fieldsClass: "..."(quoted or backtick strings),utils.Cn(...)/utils.cn(...),Cn(...)/cn(...), mixed literal/dynamiccncalls, staticreturn "..."helper strings, andconst/varstring declarations (including groupedconst ( ... )/var ( ... )blocks) whose values look like utility lists.
Go examples:
return utils.Cn(base, "px-3", props.Class)
return cn("inline-flex items-center", props.Class)return CardProps{Class: "h-8 px-3 text-sm"}const ButtonBase = "inline-flex items-center"
var ButtonPad = `px-3 py-1`
const (
ButtonRow = "flex items-center gap-2"
)return "h-8 px-3 text-sm"The extractor reads only static string literals. It does not execute Go code and ignores dynamic arguments. Each string literal argument to cn / Cn is linted as its own class list, so mutually exclusive conditional branches do not report a false UI8PX004 conflict.
Checked files is the number of supported source files scanned, including files that contained no class lists.
Tailwind v4 inset-ring, inset-ring-*, and inset-shadow-* are ring/shadow utilities, not positional inset-* spacing, so they are not checked against the spacing scale and are not grouped with inset-0. Positional inset-x-* and inset-y-* are treated as their own spacing prefixes.
By default, lint scanning skips built-in transient folders (.git, .ui8px, node_modules, dist, coverage/cache folders) and entries from the local .gitignore.
Example diagnostic:
src/views/landing.templ:24:16 UI8PX001 disallowed utility class "px-3"
Scope: layout
Reason: px-3 is not allowed in layout scope.
Suggestion: use px-2 or px-4
Rules:
UI8PX001: disallowed spacing token for current scope.UI8PX002: utility class not in allowed policy.UI8PX003: utility class is explicitly denied.UI8PX004: conflicting utilities inside the same class list.UI8PX005: unknownui-*semantic class.
npx ui8px lint ./... --learnLearn mode writes:
.ui8px/telemetry/observed.jsonl
.ui8px/telemetry/proposals.json
Plain lint does not mutate files. --learn records unknown, denied, and disallowed classes so a developer or LLM-assisted workflow can review recurring issues later.
npx ui8px validate patterns ./...This command finds repeated class-list compositions. It normalizes class order, removes internal duplicates, resolves simple conflict groups, and writes:
.ui8px/reports/patterns.json
Example:
Repeated pattern found 14 times:
flex items-center justify-between gap-4 px-6 py-4
Suggested semantic class:
ui-section-header
ui8px does not automatically generate @apply CSS. It reports candidates so semantic ui-* patterns can be reviewed intentionally.
npx ui8px validate aria ./...This command scans source files for static data-ui8kit="..." hooks and common UI8Kit component calls such as @ui8layout.Shell(...), @ui.Accordion(...), and @ui.Tabs(...). It checks that the matching @ui8kit/aria pattern is included in the site bundle. It reads the generated web/static/js/manifest.json when present, otherwise it falls back to package.json ui8kit.aria.
Examples:
data-ui8kit="dialog"requires thedialogpattern.data-ui8kit="sheet"anddata-ui8kit="alertdialog"also requiredialog.data-ui8kit="tabs"requirestabs.@ui8layout.Shell(...)requiresdialogbecause the shell owns a mobile sheet.
Use --package <path> or --manifest <path> when the files live outside the current working directory.
The existing map validator remains available:
npx ui8px validate grid --input class-map.json --output class-map.backlog.jsonIt checks CSS declaration values in a class map and reports values that do not resolve to the 8/4px grid.
0— no blocking violations.1— lint, grid, or pattern violations found.2— invalid usage or runtime error.
Before publishing:
npm run preflight
npm publish --access public --provenance