Download llms.txt from EHS-Studio/dssl: direct link, hf CLI and curl.
- Browser
- Download file 23.3 kB
-
https://huggingface.co/EHS-Studio/dssl/resolve/main/llms.txt
- Command line
-
hf download hf://EHS-Studio/dssl/llms.txt
-
curl -L -o llms.txt https://huggingface.co/EHS-Studio/dssl/resolve/main/llms.txt
23.3 kB
| # DSSL | |
| > DSSL is a style language for the web. For JS-guaranteed pages it replaces writing CSS; it does not replace the CSS platform. You write `.dssl` files, the page loads the engine (about 58 kB gzipped, about 190 kB as plain source), and the engine walks the DOM in real time applying every matched rule as inline styles. Nothing compiles to CSS at runtime, but v0.8.0 adds the exit lane: `dssl build` compiles the static subset to a real .css file for no-JS, strict-CSP or size-critical pages. Elements added later are styled the moment they appear; signals, states and motion share one grammar. | |
| - Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app | |
| - Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl | |
| - Install: `curl -fsSL https://dssl-sable.vercel.app/install.sh | sh` (or the zip: https://dssl-sable.vercel.app/dssl-engine.zip) | |
| - Version: 0.16.2 (engine), MIT license | |
| - Feature matrix vs CSS: see PARITY.md in the repo | |
| - For agents working ON the codebase: see AGENTS.md in the repo | |
| ## The five things reviewers ask about | |
| 1. No build step. Styles are source files linked with `<link rel="stylesheet/dssl">` or embedded in the page as `<style type="text/dssl">` / `<script type="text/dssl">` blocks (auto-booted in document order). The engine restyles the live DOM, including nodes added later. | |
| 2. Honest trade: it is a JS runtime. If JavaScript is unavailable the page renders unstyled, unless you build the static subset: `dssl build styles.dssl -o styles.css` emits real CSS (tokens β custom properties, states β :hover, layers β @layer, motions β @keyframes, `raw { }` verbatim, `seen` applied as its no-JS final state) and the report counts what stays runtime-only (`@signal` rules, `animate:`). For content pages that must not flash, use the build output or choose another tool. | |
| 3. Exit cost is zero. Abandoning DSSL leaves you a page of valid inline styles and one small shadow stylesheet for pseudo-elements. There is nothing to un-install from your markup. | |
| 4. Speed has a handbrake. `DSSL.freeze()` detaches every observer and listener after first paint and leaves the resolved look at zero engine cost; `unfreeze()` re-arms. A gate prefilter skips obvious non-matches before native matching. The Bench page on the site measures 100 / 1,000 / 10,000 nodes on your machine: CSS `<style>` vs DSSL live vs DSSL frozen. | |
| 5. The cascade is (layer, source order). No specificity, no !important. Nested state blocks sort after the parent rule's own declarations (v0.7.2 fix: they used to lose), and nested `@media`/`@signal`/`@layer` keep their condition. `pin { }` is the only must-win block. The compiler refuses to guess: unknown tokens, properties and values fail loudly with did-you-mean; `--*` custom properties pass through unvalidated on purpose. | |
| ## Language surface (v0.8.0) | |
| - Selectors: the full CSS surface rides native matching, including `:nth-child()`, `:not()`, `:is()`, `:where()`, `:has()`, combinators, attributes, nesting with `&`. | |
| - States as nested blocks: `hover`, `focus`, `focus-visible`, `active`, `disabled`, `checked`, `empty`, `target`, plus `seen` (fires once on first viewport entry, composes with `stagger:`). | |
| - Tokens built in: `color.*`, `space.1-8`, `radius.*`, `shadow.*`, `font.*`; custom `tokens { }` blocks with numeric keys (`space { 9: 96 }`), alpha suffixes (`color.ink/85`), per-subtree re-declaration (`--color.accent: color.err`). | |
| - Layout sugar: `row: gap space.4 between center`, `column:`, `grid: 3 gap 12`, `stack: center`, `center: both` (promotes a formatting context so alignment can never silently no-op), `size: 40 20`. v0.8.0 axis roles: distribution words (`between`/`around`/`evenly`) own the main axis, `stretch`/`baseline` own the cross axis, `center`/`start`/`end` fill free axes, and a word with nowhere to go warns instead of overwriting silently. | |
| - Pseudo-elements (`::before`, `::after`, `::placeholder`, `::selection`, `::marker`, `::backdrop`, `::first-line`, `::first-letter`, vendor ones) compile to one real shadow stylesheet; unknown pseudo names are compile errors. | |
| - Conditions: `@media` (with `dark`, `light`, `reduced-motion` sugar and `<=` ranges), `@container`, `@signal menuOpen { }` bridged from `DSSL.signal()`, `@layer base|components|utilities`, `pin { }`. | |
| - Motion: `motion name { from { } to { } }` via WAAPI; `animate: pulse 1600 infinite alternate`; `stagger: 70`; reduced-motion respected unless a motion declares `essential`. | |
| - Deform family: `bend`, `bend-y`, `bend-up`, `fold`, `stretch` (volume-honest squash), `bend-around` (children ride an arc). Compose into the one transform channel, state- and transition-friendly. | |
| - Gradients as sentences: `gradient: linear 135, color.accent -> color.ok`, `gradient-text:`, `gradient-flow: 6s alternate`. Non-color stops are compile errors. | |
| - Overlap policy: nothing below 100% opacity overlaps anything unless `overlap: allow`; `deny` is unconditional; pairs with `pointer-events: none` so veils stop eating clicks and text selection. | |
| - Behaviors: named rule groups applied with `use focus-ring`. | |
| - Verbatim escape hatch (v0.8.0): `raw { }` / `@css { }` drops real CSS (@font-face, @keyframes, @supports, @import, @property) into the page stylesheet untouched; unknown at-rules skip their whole block with one clean error. Inside raw { } semicolons are required (it is real CSS); a missing one is a warning with a line number. | |
| - Fluid sugar (v0.8.0): `font-size: fluid 16 24` (and any length-ish property) compiles to the linear clamp() between 360px and 1200px viewports (exactly the first size at 360px and the second at 1200px; px, rem or em, no percentages; shrinking sizes work); `fluid: 16, 24` still works; it also works on the longhands (`padding-top`, `margin-block`, `letter-spacing`, `border-radius`) since 0.8.2. 0.8.2 also made the cascade global: a stylesheet loaded later always beats an earlier one, and a later `tokens { }` block overrides an earlier conditional token. 0.8.1 fixed the math: 0.8.0 left out the intercept, so sizes were too large on small screens. | |
| - v0.8.0 review-driven fixes, all regression-tested incl. headless-Chromium computed-style tests of the docs: row/column axis roles (the documented `between center` example laid out wrong before); @font-face no longer crashes the compiler (unknown at-rules skip balanced); @supports inner rules no longer leak unconditionally; `url(...)` is atomic (unquoted form no longer false-errors); `!important` is a loud error that emits nothing; the animation shorthand warns when its motion is undefined instead of suggesting "span"; CSS-order transition values get the exact DSSL conversion in the error hint. | |
| - v0.7.2 review-driven fixes, all regression-tested: gate prefilter runs before native matches(); hover state events scope candidate recomputation to the touched subtree plus ancestors (was page-wide); checkbox/details states listen to `change`/`toggle`; raw hex alpha (`#ff0000/50` -> color-mix); bare numbers on time properties are ms; `::part()` supported, `::slotted()` rejected with an explanation; SVG properties (`fill`, `stroke*`) and `text-wrap`/`content-visibility` in the vocabulary. | |
| - Warnings channel (v0.7): things that compile but are probably wrong, e.g. a layout shorthand expanding to `display` and clobbering an earlier explicit `display:` in the same rule. | |
| ## JS API | |
| ```js | |
| DSSL.load(['/styles/site.dssl']) // fetch + apply, in order | |
| DSSL.apply(src, { scope, name }) // scoped engines for widgets | |
| DSSL.compile(src) // parse + resolve, no DOM (powers editors) | |
| DSSL.toCSS(src) // v0.8.0: the exit lane -> { css, report } | |
| DSSL.signal('menuOpen', true) // the JS -> styles bridge; DSSL.signal(name, value, el) for one subtree | |
| await DSSL.settled() / DSSL.flush() // style everything waiting now (offsetTop is final after it) | |
| DSSL.freeze() / DSSL.unfreeze() // the speed handbrake | |
| DSSL.explain(el, { quiet: true }) // matched rules, file/line, winning decls per property | |
| DSSL.replay(sel) / DSSL.refresh([el]) / DSSL.errors() (a function) / DSSL.version (a string) / DSSL.getSignal(name) / DSSL.freeze([el]) / DSSL.unfreeze([el]) / await DSSL.inspect() / await DSSL.watch() / await DSSL.lint(root) / DSSL.play(el, motion, opts) / DSSL.motion('full'|'reduced'|'auto') / DSSL.auraZ(n) / DSSL.computed(el, prop) / DSSL.pauseMotion() / DSSL.stepMotion(ms) / DSSL.resumeMotion() | |
| ``` | |
| ## 0.9.0: texture, aura, sliders, lint, import | |
| - `texture: grain` layers a generated image ON TOP of an element's own background (colour, gradient or several layers). 40 presets (30 general: grain, noise, rough, sand, paper, cloud, concrete, stone, crumpled, glitter, frost, marble, wood, bark, leather, brushed, canvas, linen, denim, carbon, scanlines, halftone, dots, lines, grid, checker, crosshatch, stripes, waves, hex) and 11 generators (noise, bump, lines, dots, grid, checker, weave, carbon, waves, hex, rings) with settings: scale, figure, warp, pores, knots, planks, intensity, contrast, threshold, octaves, seed, stretch x y, depth, light, angle, thickness, radius, softness, contour, animate, type, blend, color, color2, invert. Comma stacks layers. Built at run time, and baked into the CSS by `dssl build` when its colours are known (0.11.0). | |
| - Aura has NO presets. `aura-glow: <color> <radius> [intensity|pulse|depth|flicker|texture(...)|seed|essential]`, `aura-particle: <color -> color ...> <count> [size|life|speed|dir|spread|gravity|drag|turbulence|from|shape|grow|glowing|behind|blend|texture(...)|seed|essential]`, `glow-particle` (particles that glow), `aura-glow-particle` (both slots), `aura: glow ..., particles ...` (replaces both; `aura: none` clears both, `aura-glow: none` only the glow). Name your own with `behavior fire { ... }` then `use fire`. One canvas over the page, 1800 particle budget, still glow under reduced motion, runtime-only. | |
| - `slider: track 6 color.line radius full, fill color.accent, thumb 24 color.paper, ring 3 color.accent, glow color.accent 14, ticks 10, graph 20 45 70` writes the ::-webkit-slider-* and ::-moz-range-* rules together for `input[type=range]`. The engine keeps `--dssl-p` (value 0..1) on the input. Firefox rules are generated but untested. | |
| - Lint warnings (never errors; `dssl check --strict` makes them fatal): text contrast under 4.5:1 (3:1 for large text), a removed focus outline with no focus-visible replacement, tap targets under 24px. | |
| - `dssl import styles.css -o styles.dssl` converts CSS to DSSL and reports what could not carry over; the output always compiles. | |
| - The editor extension has a designer panel (sliders, live previews, graphs, DSSL output, Insert and Load from selection), completions for the new vocabulary, and a CSS converter command. | |
| ## 0.10.0: glass, tilt, named-colour alpha | |
| - `glass: 18 saturate 1.6 tint white/12` writes backdrop-filter plus a faint wash of the text colour. Plain CSS, kept by `dssl build`. Settings: blur number, `saturate <n>`, `bright <n>`, `tint <color>|none`. `glass: none` clears it. | |
| - `tilt: 10 lift 1.04 perspective 900 speed 0.18 invert glare` turns toward the pointer in 3D. Runtime only. `glare` keeps `--dssl-gx` and `--dssl-gy` pointing at the pointer. Ignores touch, flat under reduced motion, stops on freeze. Do not combine with `bend` or `fold`. | |
| - Alpha on a named colour works: `white/12`, `currentColor/20`. It used to compile to `white/12px`. | |
| - The playground has a canvas.html tab, `?example=` links, lint warnings and an "as CSS" tab. The canvas keeps a short allow-list of tags and drops scripts, handlers, styles and ids. | |
| ## 0.16.2 | |
| - `DSSL.lint`'s `css-loses` no longer judges the output of `dssl build` (its reset is built to sit under the engine), and ignores a rule whose value is the same as what DSSL wrote inline. On this site's docs page it reported 4,314 findings in 0.16.0; it now reports none from the site's own CSS. | |
| ## 0.16.0: round three | |
| - `yield: padding-top padding-bottom` hands properties back to plain CSS (the engine writes inline styles, which beat a stylesheet): the CSS wins with no `!important`. `yield: padding` covers the family, `yield: none` clears it. `DSSL.lint` reports `css-loses` when your CSS rule loses to DSSL, naming the fix. `dssl build` ignores `yield`. | |
| - Valid CSS is accepted: `env(safe-area-inset-top)`, `font-variant-numeric: tabular-nums`, `touch-action`, `scroll-snap-type`, `-webkit-user-select`, `direction` and the rest. The known words and properties are generated from the MDN data (`node scripts/gen-css-keywords.mjs`). A typo still errors with did-you-mean. | |
| - `tokens { radius { pill: 999 card: 22 } }` on one line is two tokens; a token error points at its own line. | |
| - Aura follows the real shape: it reads `clip-path`, `border-radius` (also `50%`) and rotate/scale/transform, so a diamond, a star or a turned square glows in its own shape. `around box|round|pill|diamond|triangle|hexagon|octagon|pentagon|star|star4|star6|cross|heart|drop|bolt|alpha|polygon(50% 0, 100% 50%, 50% 100%, 0 50%)`; `alpha` follows an <img> or <canvas> pixels. Particles `from edge` or `from outline` are born along that outline. | |
| - More aura: glow `inner`, `rays N` + `spin`, `ripple ms`, `nohalo`; particle shapes heart, diamond, triangle, plus, drop, bubble, snowflake, confetti, leaf; particle `palette`, `spin`, `wind`, `orbit`. | |
| - 33 new texture presets (73 in all: bricks, herringbone, tartan, gingham, scales, mermaid, shingles, chevron, argyle, lowpoly, tiles, blueprint, granite, slate, sandstone, terrazzo, confetti, rust, corrugated, cork, kraft, felt, burlap, stucco, starfield, snow, grass, water, rain, lava, topo, bricks-old, plaid) and nine generators (bricks, herringbone, plaid, scales, chevron, diamonds, triangles, starfield, terrazzo) with `density`. | |
| ## 0.15.0: the second game review | |
| - Aura: it follows moving things under reduced motion (a quick look every 100ms, a full frame whenever something moved), skips emitters that cannot be seen (opacity 0 or display: none or visibility: hidden on it or any ancestor, and what they let go fades with them; the word `always` keeps an invisible anchor emitting), and takes `z N` to draw a layer on its own canvas at that z-index (`aura-glow: #f80 40 z 5` goes under a hill with z-index 10; it meets elements in the root stacking context). | |
| - `dssl check` and the console name rules that can never apply: a rule that a LATER rule matches everywhere and overrides (`.prop.broc { z-index: 1 }` before a plain `.prop { z-index: 2 }`; there is no specificity). The same selector again is left alone, that is an override. | |
| - Signals keep their type (`getSignal('x')` is `true`, not `"true"`; `0` is off). `DSSL.signal(name, value, el)` scopes one to a subtree (the nearest wins). A signal restyles only the elements its rules select. `await DSSL.settled()` / `DSSL.flush()` make styling final right away. | |
| - `animate: blink 300 infinite, waddle 900 infinite alternate` runs several motions on one element (also on ::before and ::after, and in `dssl build`). `DSSL.play(el, 'pop', { pseudo: '::after' })`. | |
| - `outline-shape: 3 color.ink` outlines the shape's alpha, not its box (four hard drop-shadows, square corners; uses `filter`). `rotate: 5` is 5deg. A state inside a pseudo-element block (`::after { hover { opacity: 1 } }`) is that pseudo-element in that state. `font: bold 16px Arial` is now an error that says DSSL's `font:` is size/line-height and colour. | |
| - `DSSL.lint` skips invisible text and judges text-stroke and tight text-shadow. `dssl ... --engine ./dssl` (or DSSL_ENGINE) finds the engine anywhere. `DSSL.pauseMotion()`, `stepMotion(ms)`, `resumeMotion()` and `DSSL.computed(el, prop)` for tests. | |
| - Gradients: the first layer is the one on top. | |
| ## 0.14.0: made for games (from a review by an agent that built one) | |
| - Reduced motion is never silent. A plain `animate:` does not run when the system asks for reduced motion; the console now says so once, and says how to get out: put `essential` in the motion block or on the animate line, or `DSSL.motion('full')` / `<html data-dssl-motion="full">` to run everything (`'reduced'` holds all still, `'auto'` follows the system). | |
| - `animate: name 300 delay 100 jitter 200 iterations 3 essential`. `jitter` gives every element its own random delay, the same on every load. `animation-delay` and friends beside `animate:` now warn, because they do nothing there. | |
| - Motions are shared across files like tokens (`dssl check`/`build` too). Keyframes accept `0%`, `40%`, `0%, 100%`, `from`, `to`. Every motion also exists as a real `@keyframes`, so `animation: bob 300ms` works. `animate:` works on `::before` and `::after`. | |
| - `await DSSL.play(el, 'boing', { duration, hold })` returns a promise that settles when the motion ends (also when cancelled); `hold: true` keeps the last frame; an unknown name rejects with did-you-mean. | |
| - `gradient:` layers with `+`, takes CSS gradient functions as layers, and `tile 24`, `offset 4 4`, `no-repeat` after a layer. `gradient:` plus `background-image:` in one rule is a warning. | |
| - `text-stroke: 6 color.ink` (outside the letters, paint-order stroke fill), `inside` for centred, `none`. `paint-order: stroke fill` and `conic-gradient(from 0deg, ...)` are accepted. `shadow: pop` and `radius: pill` work for your own tokens. | |
| - Your own inline styles win: a `style=""` attribute or a script's `el.style.left` beats a rule, including through `DSSL.refresh()`. `data-dssl-static` / `DSSL.freeze(el)` styles a subtree once and leaves it alone; `DSSL.refresh(el)` restyles it on demand. | |
| - Aura: clipped to an `overflow: hidden` stage (add `unclipped` to spill), scales with an ancestor `transform: scale`, `DSSL.auraZ(20)` puts the canvas under a menu. | |
| - `@signal screen in (menu, shop)`, `not in (...)`, `!=`. | |
| - `DSSL.lint(root)` finds what only a live page shows: text colour against the composited background (cream on cream from two rules), and elements the engine had to turn into grids. `dssl check index.html` and `dssl build index.html` read the stylesheets a page links, in page order. | |
| - The folded-border console message is gone. Docs now list every function on DSSL (a test keeps it that way). | |
| ## 0.13.0: shipping without the engine | |
| - `dssl build a.dssl b.dssl -o out.css [--reset] [--sourcemap] [--inline-tokens] [--native-specificity]`: several files in load order. Selectors are wrapped in :where() so source order wins like in the engine (no CSS specificity). --reset adds the small reset as the FIRST layer. --sourcemap writes out.css.map (rule and declaration level, sources embedded) so devtools show the .dssl file and line. --inline-tokens writes token values in place of var() where a token is defined once and never redefined (fastest; tokens changed at run time will not reach built rules). | |
| - Proof: scripts/test-static-parity.mjs compares every page of the site, built CSS with JavaScript off vs the engine, ~100 properties per element plus box and pseudo-elements, 5,000+ elements: identical. Engine-only features (animate, aura, tilt, bend, stretch, overlap, gradient-flow, stagger, signals, slider fill, texture shimmer) are not in a build. | |
| - Numbers (scripts/bench-compiled.mjs): compiled page startup = hand-written CSS, churn +7% (0% with --inline-tokens), restyle +20% (0% inlined). The engine is NOT at parity: load event 17x, churn 4x. | |
| - Strict CSP: the engine uses the CSSOM and constructed stylesheets, never a script-made <style>; nonce honoured on the fallback path. Textures need img-src data:. | |
| - Server rendering: public/dssl/server.js `compileSheets([...], opts)` and `styleTag(css, {nonce})`, no DOM needed. | |
| - Fixed in the engine and found by that comparison: a border-style/width written after `border`, and a margin or padding longhand versus its shorthand, now follow the cascade. New check warning: a rule that aligns children without display. | |
| ## 0.12.0: fonts, gradients, symmetrical, dev tools | |
| - `texture: oak symmetrical [x|y|both]` mirrors every other tile: no seam, the repeat is twice as far apart. Works on image textures (noise, bump, rings, waves, hex); pure-CSS patterns are left alone. | |
| - `fonts { Fraunces: 500 700 800 "Hanken Grotesk": 400 700 italic Inter: 100..900 display optional Brand: from "/f.woff2" 400 }`. Google families become a <link> at run time and an @import in `dssl build`; `from` becomes @font-face. | |
| - gradient: `repeating linear 45, #fff 0 10, #000 10 20`; `linear 90 in oklch, a -> b`; `a -> 30% -> b` midpoint; `a 0 30%, b 30% 100%` hard bands; `mesh base, color at x y [size n], ...`; layers joined with ` + `; the direction is optional. | |
| - `DSSL.inspect()` (which rule produced each style, in the page), `DSSL.watch()` (hot reload of loaded .dssl and linked .css, no page reload), `node cli/dssl.mjs dev [dir]`. `data-watch`/`data-inspect` on the script tag, or ?dssl-watch / ?dssl-inspect. | |
| ## 0.11.0: wood and the build | |
| - Ten wood species presets: oak, walnut, cherry, maple, pine, mahogany, ebony, birch, oak-planks, walnut-planks. The `rings` generator draws growth rings as vector curves (not stretched noise): `texture: oak figure 0.8 knots 2 planks 120`. Settings: scale (ring spacing), color (latewood), color2 (earlywood), contrast, figure (cathedral arching), warp, pores, knots, planks (board height px), seed, angle 0 or 90. Same seed, same board everywhere; the tile repeats without a seam. | |
| - `dssl build` now BAKES textures into the CSS as background images when every colour is known at build time. Animated textures and unknown colours stay in the runtime, and the report says why. | |
| - `counter-reset`, `counter-increment`, `counter-set` and `content: counter(name)` / `counters(name, ".")` work. | |
| - The contrast lint no longer cries wolf on translucent backgrounds: it warns only when the pair fails on the light page colour, white and black. | |
| - A page can hide itself until styled: `<style>html:not(.dssl-ready) body{visibility:hidden}</style>`; the engine adds `dssl-ready` to `<html>`. | |
| - Showcase: /showcase/wood/ is a whole page (Heartwood) built with these. | |
| ## CLI (v0.8.0) | |
| ```sh | |
| node cli/dssl.mjs check styles.dssl --strict # CI: exit 1 on errors, --json for machines | |
| node cli/dssl.mjs build styles.dssl -o styles.css # the static subset as real CSS | |
| ``` | |
| `check` runs the real compiler with file:line diagnostics and did-you-mean hints. `build` compiles the static subset (tokens β custom properties, states β :hover, layers β @layer, motions β @keyframes, raw { } verbatim, pin β !important, seen β its no-JS final state) and prints a report of what was emitted and what stays runtime-only. | |
| Wait for the `dssl-ready` event on `window` before measuring layout: the engine styles the DOM after DOMContentLoaded, and scripts that measure earlier read a half-styled world. | |
| ## Editor support | |
| The official extension (VS Code and VSCodium, same .vsix) bundles the real parser and resolver: diagnostics identical to the browser console, completions from the workspace token registry, hover docs for the semantic vocabulary. In VSCodium, search "DSSL" in the Extensions tab (it is on Open VSX: https://open-vsx.org/extension/ehs-studio/dssl-vscode). In VS Code, download `dssl-vscode/dssl-vscode-0.16.2.vsix` from the repo files, then Extensions panel -> ... -> Install from VSIX. Source lives in `dssl-vscode/` in the repo. | |