Current version: 0.6.30.0 (pre-1.0).
See CONTRIBUTING.md for repo layout, gotchas, build commands, and versioning. See ROADMAP.md for pending work.
The philosophy is frozen. See
PHILOSOPHY.md— the immutable constitution. Every change is evaluated against the seven pillars. CI enforces compliance viabin/check-philosophy-drift.sh.
The version is frozen at
0.6.x.x. Token & variable system must be finished, tested, and DX-validated before0.7.0.0. Seedocs/VERSION-FREEZE.mdfor unlock conditions.bin/bump-version.shrejects≥ 0.7.0.0withoutFREEZE_UNLOCK.
Run
bin/sync-docs.shto verify all documentation, tokens, and integrity checks pass in one command.
SLASHED = Standalone · Lean · Agnostic · Structured · Hybrid · Edgeless · Deterministic.
BEM-first authoring. Everything is written in BEM — this is the methodology, not a component library. The shipped .sf-* components are an opt-in convenience layer: they pre-bake the most common patterns (buttons, cards, forms, …) so the consumer doesn’t re-author them, but they are not part of the essential bundle (slashed-essential.css ships only tokens + core — the frozen source-of-truth surface; slashed-full.css is the everything bundle including utilities, components, visual utilities, animations, and viewport utilities). They are not “the way” to use the framework. The consumer can ignore them entirely and author their own unique BEM blocks (e.g. .product-card, .site-header) styled with framework tokens (var(--sf-space-m), var(--sf-color-surface), var(--sf-radius-m), …). Both paths are equally idiomatic. The naming rules (__, --, no element-in-element, kebab-case) apply identically in both cases. Layout primitives wrap BEM blocks to compose pages. Utilities are subordinate helpers — reach for them only for layout tweaks and one-off adjustments where authoring a BEM class would be overkill. If a pattern repeats, it becomes a BEM block, not a stack of utilities.
Mobile-first responsive model. All breakpoint prefixes (sm:, md:, lg:, xl:) use min-width — styles apply at the named breakpoint and up. Write the mobile layout first, then layer in breakpoint overrides. Responsive design in SLASHED has three layers, in order of preference: (1) fluid tokens — --space-*, --text-*, and --leading-* tokens use clamp() and scale continuously with the viewport; reach for them first and you need no @media rule at all. (2) container-aware primitives — .grid, .stack, .cluster, and other layout primitives respond to their own container width via @container, not the viewport; they handle most structural layout automatically. (3) breakpoint utilities — sm:, md:, lg:, xl: prefix classes handle the remaining discrete viewport-level switches (show/hide a nav, flip a flex direction, change column count). Reach for (3) only when (1) and (2) are not sufficient.
Standalone — No build step, no Node, no npm, no runtime dependencies. The consumer links a CSS file. Any change that introduces a preprocessor, build tool, or runtime dependency on the consumer side is permanently out of scope.
Lean — Every class earns its place. Additions are at the maintainer’s discretion. Does it earn its bytes? Is it composable from existing classes? Decorative utilities belong in slashed-utilities-visual.css, not core.
Agnostic — Works on any platform that can load a CSS file. Framework integrations (Bricks, WordPress) are additive and opt-in, never a dependency.
Structured — Cascade layers enforce specificity order. BEM is the canonical naming scheme — .block__element--modifier, with .sf-* reserved for framework blocks; see CONTRIBUTING.md → “Authoring order and BEM naming” for the full rules. Token hierarchy is explicit. New components follow the same layer, naming, and token conventions as existing ones.
Hybrid — Three authoring layers in a fixed hierarchy: BEM is the default authoring style for everything (framework .sf-* components and consumer-authored blocks alike); layout primitives wrap BEM blocks to compose pages; utilities patch the gaps. Two interactivity paths (pure-CSS baseline + optional JS enhancements). Every interactive component must ship a meaningful pure-CSS path first. HTML-first: reach for <details>, <dialog>, :has(), and native form elements before writing JavaScript.
Edgeless — The framework never blocks the consumer. Unlayered BEM always beats framework layers — no !important needed. Instance tokens allow single-line component modifications. If a consumer needs !important to override something, that is a framework bug.
Deterministic — Tokens drive the system. A given token input always produces the same output. New tokens must follow existing derivation patterns (color-mix(), light-dark(), clamp()). Magic numbers in framework CSS are bugs — every value a consumer might want to change must be a custom property.
For every framework-surface change (new/renamed/removed class, token, or JS API):
CHANGELOG.md (append-only, Keep-a-Changelog format).### Breaking with a one-line rename map.cheatsheet.html in the same commit. The pre-commit hook blocks commits that stage CSS/JS source files without also staging cheatsheet.html. (Note: this enforces file-level co-staging only — class-level parity is not yet checked; see ROADMAP bin/check-cheatsheet-mapping.sh.)When implementing any item tracked in ROADMAP.md:
ROADMAP.md. The CHANGELOG entry is the permanent record — do not add a “resolved” section to ROADMAP.md.At every version bump:
bin/check-version-sync.sh to verify CLAUDE.md and CONTRIBUTING.md version lines match the latest CHANGELOG.md entry.0.6.x.x is non-negotiable — bin/bump-version.sh enforces it.Before submitting any PR:
bin/sync-docs.sh to verify all integrity checks pass.bin/check-philosophy-drift.sh to confirm no pillar violations.bin/check-token-parity.sh to confirm token coverage is maintained.slashed.overrides is intentionally empty. Framework source must never author rules into it. It exists so consumers can beat every framework layer without !important.
.sf-grid-cols reads --sf-cols, not --sf-grid-cols. Setting --sf-grid-cols: N on .sf-grid-cols does nothing — that token is the knob for the .sf-grid primitive instead. For one-line column grids use <div class="sf-grid-cols" style="--sf-cols: 3">.
Viewport breakpoints are hardcoded in slashed-utilities-viewport.css only. CSS forbids custom properties inside @media. The four values (sm: 30em / md: 48em / lg: 64em / xl: 80em) live in that file. Layout primitives in slashed-core.css use @container and are unaffected by breakpoint changes. Consumers who need custom breakpoints link slashed-core.css + their own modified copy of slashed-utilities-viewport.css.
Pre-0.8.0.0: no migration overhead. Anything on the public surface can be renamed, removed, or restructured in a single commit — no deprecation alias, no migration guide, no advance notice required.
--warning on white fails WCAG AA (~2.8:1). Use --warning-600 for warning text. Pair --warning-100 background with --warning-600 text for warning messages.
:root:root { font-size: max(16px, 1em) } is defensive — defeats WordPress/Bricks themes that set 62.5% on :root. The doubled selector lifts specificity to (0,2,0). Keep it separate from the main token block. Do not “simplify” it.
hanging-punctuation: first last is set globally on html. Intentional — benefits all text, not just .sf-prose. Ignored by non-supporting browsers (zero risk). Can produce subtle optical offsets on UI chrome text in fonts with significant punctuation overhang (CJK locales, certain monospace fonts inside <pre>). Opt out per element with hanging-punctuation: none.
.sf-modal--card uses 90svh, not 90vh so the footer is not clipped behind mobile browser chrome (URL bar). Baseline support is wide (Chrome 108+ / Firefox 101+ / Safari 15.4+).
Bare <select> chevron is not token-driven. The SVG data URI embeds hardcoded hex values (#4b5563 light / #9ca3af dark) because currentColor is not available inside background-image SVGs. Consumer overrides of --sf-neutral do not propagate. Workaround: use .sf-select-wrap (mask-image-based, inherits currentColor).
.sf-drawer pure-CSS state requires direct sibling layout. .sf-drawer__input + .sf-hamburger + .sf-drawer + .sf-drawer__backdrop is an adjacent-sibling chain — any element inserted between breaks the selector silently. For nested DOM hierarchies use slashedUI.initDrawers() with explicit aria-controls instead.
Pure-CSS caps on .sf-tabs (10 panels) and .sf-stepper--linked (6 steps). The :has(:nth-child(N)) cascade is hand-authored for those counts; extra panels/steps render but their CSS-driven active state never fires. For larger counts use slashedUI.initTabsAccessible() (assigns .sf-is-active).