quark
CSS-like orchestration for your HTML — bind attributes, render lists, and wire events without a component tree.
Quark is a derivative of CSS, written in CSS syntax. Selectors, nesting, declarations and comments are CSS's own; the few additions Quark makes, such as variables, expressions and at-rules of its own, stay compatible with that syntax, so anyone who can read a stylesheet can read a sheet. What differs is the runtime: a stylesheet paints, a Quark sheet writes State — attributes, content and variables on the elements it matches. Media queries, keyframes and the rest of the browser's style engine stay in your stylesheet.
Prefer <quark-sheet> for apps; use the Quark class when you need a programmatic host (tests, tooling).
Features
- CSS-like sheets Selectors + nested rules that mutate the live DOM
$variablesScoped values that nest and resolve in expressions- JS writes
element.quark.setProperty()hands values app JS already holds to the document as$variables - CSS variables Write
--custom-propsfrom state; style viavar() - Content rendering
content,template(),iterate(),dangerous-html() - Element properties
prop("provision")reads Neutron provisions / any JS property, re-running on assignment - Events
@onat-rules with delegation, key, timing and host options, plusprevent-default/stop-propagation - View transitions
@view-transitioncommits a block's writes insidedocument.startViewTransition(), so CSS animates list changes, removals included - Delayed writes
@delay 2000 { … }applies a block after a pause — flashes, toasts, undo windows — dropped if the rule stopped matching - Diagnostics
@warn/@debug/@errorreport from a rule; the selector is the condition - Built-in modules
@use "quark:math",quark:list,quark:map,quark:string,quark:date,quark:url,quark:util— pure helpers, imported like JS modules - Attribute helpers
dataset,ariaset,class,noneto clear - JS modules Pure functions from
@use "/url", called in expressions - Scoped host Sheet + targets share a parent; updates follow DOM mutations
- DevTools
Quark.attachDevtools()reports rule applications and$variablesto the Nucleus DevTools extension
Installation
This package is available in the
<script src="https://unpkg.com/@excom/kit-utils@0.3.0/dist/index.umd.min.js"></script>
<script src="https://unpkg.com/@excom/quark@0.4.1/dist/index.umd.min.js"></script>npm install @excom/quarkimport { /* … */ } from "@excom/quark";Peer dependencies (0)
Packages a consumer must install alongside this one. Workspace deps are bundled.
| Package | Version |
|---|---|
|
Usage
App authors almost always load Quark through <quark-sheet>:
<section>
<quark-sheet>
details[open] [bind-status] {
content: "Open";
}
details:not([open]) [bind-status] {
content: "Closed";
}
</quark-sheet>
<details>
<summary>Panel</summary>
<span bind-status></span>
</details>
</section><section>
<quark-sheet>
details[open] [bind-status] {
content: "Open";
}
details:not([open]) [bind-status] {
content: "Closed";
}
</quark-sheet>
<details>
<summary>Panel</summary>
<span bind-status></span>
</details>
</section>Programmatic API (tests / custom hosts):
import { Quark } from "@excom/quark";
const quark = new Quark({
src: `span { content: "four times two equals #{twice(4)}"; }`,
});
quark.register({
sheetElement, // host = sheetElement.parentElement
modules: { dfault: { twice: (n) => n * 2 } },
});
// …
quark.unregister();import { Quark } from "@excom/quark";
const quark = new Quark({
src: `span { content: "four times two equals #{twice(4)}"; }`,
});
quark.register({
sheetElement, // host = sheetElement.parentElement
modules: { dfault: { twice: (n) => n * 2 } },
});
// …
quark.unregister();Documentation
Syntax
Sheets & scoping —<quark-sheet>,@scope,is-global, what runsSyntax — rules, declarations, literals, operators,if()
Selectors
Selectors — combinators and pseudo-classes, what is observedReactivity — when a rule re-runs, loops, timingNo reversion — write the inverse rule
Declarations
Declaration kinds — what a key doesAttributes — attributes,class/dataset/ariaset, form controlsContent —content,template(),iterate(),dangerous-html()CSS variables —--custom-props
Values & expressions
Variables —$variables, cascade,unset, raising stateWriting from JS —element.quark.setProperty()Values & keywords —none/preserve/unset, wipes and no-opsExpressions — name resolution, operators,if(), lists and mapsBuilt-in functions —attr(),prop(),iterate(),event, …Allowed methods —.toFixed(),.join(), …Built-in modules —@use "quark:math",quark:list,quark:map,quark:string,quark:date,quark:url,quark:utilElement properties —prop("provision"),element
At-rules
At-rules — the ones that run — JS modules, asynchronous work@use — events, blocks, options@on — outgoing events and commands@dispatch/@command — animated writes@view-transition — deferred writes@delay — diagnostics from a rule@warn/@debug/@error
Runtime
JS API —Quark,whenSettled(), DevToolsLoop guard — runaway chains are cutLimitations — beta limits and pitfalls
The grammar (EBNF, precedence, AST) is the quark-parser package's Language reference.
Examples
React to element state
Native element state drives content — no JS, no listeners:
List from a provider
prop("provision") pulls the fetch payload; iterate() renders a row per item and re-renders on every provision:
Call a module helper
<quark-sheet>
@use "/helpers.js" as *;
#out { content: formatPrice($amount); }
</quark-sheet><quark-sheet>
@use "/helpers.js" as *;
#out { content: formatPrice($amount); }
</quark-sheet>Hand a value from app JS
App JS that already holds a rich value (feature flags, a messages dictionary) hands it to the document with element.quark.setProperty(), and every rule reading that $variable below the element re-runs. See
Attributes
Role —option is configurable, state
is managed by the element (read-only), or hybrid which is both.
Provision
Thisprovision property lives on the DOM node.
Read / watch it with Quark’s prop("provision"), or listen
for the neutron-provision event from app JS.
Events
Type
command event (HTML Command API): from a
<button command commandfor>, an <event-handler
command-name>, or a CommandEvent.
Recognized Elements
Child or descendant elements are recognized by and relevant to its functionality.Styles
@custom-selector synonyms so custom elements can share
semantic styles. Element aliases match the host tag / .tag-* class; state
aliases match attributes (nested under the element alias).
Release notes (7)
0.4.1
- Fix a
@usemodule with a top-levelawaiton Safari: sheets that import one module while it loads now share that load. WebKit resolved the later imports before the module had finished, so its functions ran too early on a page's first load - Fix the
iterate()description for a non-collection, the Programmatic API samples (modulesgo toregister()) and how the Diagnostics page says to raise the log level
0.4.0
- Add hydration of prerendered pages:
iterate()rows are adopted by theirq-key, content painted bytemplate()ordangerous-html()is kept when its source matches, and a$bindingnobody has written yet keeps the server's paint until hydration ends - Improve paints when two rules write the same attribute,
classstring or textcontentof one element in one pass: only the later one writes, so the element never shows the replaced value and a replaced write starts no view transition - Fix an issue where a rule rewrote an attribute whose value already read the same as text (
data-n: 0overdata-n="0") - Fix an issue where
Quark.whenSettled({ timeout: Infinity })resolved"timeout"at once: it now waits with no cap - Fix an issue where a
@warnor@errorwhose value was a preserve reported nothing later: it now stays silent and reports on a later run that has a value
0.3.0
- Quark.meter exposes the engine's work counters for complexity snapshots
- Fix the published type declarations:
index.d.tspointed at a folder that is not in the tarball, so consumers gotanyfor named re-exports and missing-member errors forexport *instead of the real types
0.2.0
- Remove promise support from
content:. A promise that a module function returns tocontent:is refused with a console error and the content stays as it was; return a value or a node instead.template(),iterate()and@view-transition (until: …)wait as before. - Update
handle:in@onto name the listener:eventandtargetare not in scope inside ahandle:expression. A call inhandle:runs on every event and must return the listener, which receives the event withthisbeing the element. - Add the key names
Space/Spacebarandplustokey:in@on("Shift+Space"); akey:holding only spaces now warns and namesSpace - Add absolute
http(s)URLs to@use: the module loads from that URL (another origin needs CORS), and any other scheme (data:,blob:) is refused with a logged error - Add
QuarkLoggerto the exports of@excom/quark:QuarkLogger.level = 2shows the warnings Quark logs - Improve element insertions so they run rules only for the inserted elements, plus rules that depend on children or sibling position, instead of re-running every rule over the parent's whole subtree. A dialog or popover opening under a sheet's host no longer re-applies its rules (or restarts their
@delaytimers). - Add a console warning when
handle:evaluates to a value that is not a function - Fix an issue where a rule reading a
$bindingorprop()did not apply its value again when an attribute or child change made it match again, so an inverse rule left the old value in place. Where two rules reach the same key, the later one now wins for bindings, as it does for literals. - Fix an issue where values indexed by
attr(), such as$names[attr("data-key")], did not update when the attribute changed
0.1.3
- Declare the MIT license in package.json (was ISC), matching the repo LICENSE and every other package
0.1.2
- Fix
@delayblocks inside a bare:scoperule (or a:scope > …rule) never firing: the re-check at fire time handed the empty host compound tomatches(), which throws on an empty selector
0.1.1
- Ship only dist (and declared extras) in the npm tarball; drop build logs, tests and sources
Release notes
e.preventDefault() is not
synchronously called on the event.
all: revert-layer.
Valence.css ships this as .unstyled and .unstyled-all for the entire tree.