@use "/shell" as *; @use "quark:list" as list; #release-notice { /* opens once: sheet re-runs restart a delay, so gate it on the fact it writes */ &:not([data-did-open]) { @delay 7000 { is-open: ""; data-did-open: ""; } } /* prevent clicks from bubbling to the sheet */ @on mouseup, click (stop-propagation); } provider-fetch[api-url*="package-metas/index.json"][is-success] { $package-indices: prop("provision").body; /* `excom.navGroup` moves a package out of its type's list, into that group */ $ungrouped: list.reject($package-indices.packages, "navGroup"); .package-links { [bind-elements] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "kit-element")); } [bind-element-bases] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "element-base")); } [bind-tools] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "tool")); } /* the standalone libraries: same rows, rendered flat (shell.css) */ [bind-libraries] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "library")); } [bind-library-group] ul:not(ul ul) { content: iterate(list.filter($package-indices.packages, "navGroup", "libraries")); } ul:not(ul ul) > li { $pkg: item.shortName; > spa-a { route-href: "/packages/#{item.shortName}"; } } /* one `content` rule per link: a second one would rewrite the first's text on every pass */ ul:not(ul ul, [bind-libraries] ul) > li > spa-a { content: item.shortName; } [bind-libraries] ul:not(ul ul) > li > spa-a { content: displayName(item.shortName); } /* packages whose docs span several pages: one collapsible group per section */ [bind-sections] { content: iterate(item.docSections); summary { content: item.title; } details > ul { content: iterate(item.docs); spa-a { route-href: "/packages/#{$pkg}/#{item.name}"; content: item.title; } } details:not(:has(spa-a[is-active])) { open: none; } } } /* page titles: the page, then the site. The docs home keeps the title in the document head; the other routes carry theirs in markup */ spa-route[is-active] { /* a site guide */ &[route-href$=":name"] { $title-guide: list.find($package-indices.docs, "name", $route.params.name); document-title: "#{$title-guide.title or $route.params.name} · Nucleus · docs"; } /* a package README */ &[route-href$=":packageName"] { document-title: "#{displayName($route.params.packageName)} · Nucleus · docs"; } /* one page of a package's docs */ &[route-href$=":docName"] { $title-package: list.find($package-indices.packages, "shortName", $route.params.packageName); document-title: "#{docTitle($title-package, $route.params.docName)} · #{displayName($route.params.packageName)} · Nucleus · docs"; } } /* the page's markdown file, for the footer link: the docs home and each guide the index lists, and a package's page whose index entry says `markdown` (build-docs-index sets it; the dev server's index has none, so no link there). siteDocHref and the path check say which URL is the page's own: the 404 and a package's doc pages have none */ > spa-manager[active-url] { $page-path: attr("active-url").split("#").at(0).split("?").at(0); $page-name: if($page-path == SITE_HOME: SITE_HOME_DOC; else: $page-path.split("/").at(-1)); $page-guide: list.find($package-indices.docs, "name", $page-name); $page-package: list.find($package-indices.packages, "shortName", $page-name); $page-markdown: if($page-guide and siteDocHref($page-guide.name) == $page-path: "/docs/#{$page-guide.name}.md"; $page-package.markdown and "#{SITE_BASE}/packages/#{$page-package.shortName}" == $page-path: "/#{$page-package.shortName}.md"); footer [bind-page-markdown] { href: $page-markdown; content: ternary($page-markdown, "Markdown version of this page"); } } } spa-route { $route: prop("provision"); /* paramless routes name their guide in markup (the docs home, `/`); /docs/:name gets it from params */ $route-doc-name: attr("data-doc-name"); } /* the desktop aside and the mobile sheet stamp the same nav template */ [data-site-nav] { details:has(spa-a[is-active]) { /* not kosher */ open: ""; } spa-a[is-active] { aria-current: ""; } spa-a:not([is-active]) { aria-current: none; } } /* mobile sheet: Escape / back gesture while open; a tapped link closes it */ #site-menu { &[is-open] dismiss-watcher { is-active: ""; } &:not([is-open]) dismiss-watcher { is-active: none; } @on click (target: "spa-a") { is-open: none; } } #site-menu-gesture { &:has(> #site-menu[is-open]) { progress-offset: 1; } &:not(:has(> #site-menu[is-open])) { progress-offset: 0; } @on gesture-handler-start { #site-menu { is-scrubbing: ""; } } @on gesture-handler-end { #site-menu { is-open: event.detail.snap == 1; is-scrubbing: none; } } } main { @on copy-source (handle: copySource); } #search-dialog[open] { > include-content { is-active: ""; /* for some reason this is necessary on first render */ @on include-content-did-render (handle: focusInput); } input[type="search"] { /* works on all subsequent opens */ autofocus: ""; } } #search-dialog:not([open]) input[type="search"] { autofocus: none; }

Best Practices

Short rules with rationale. They exist because the document is the state — keep that document honest and the rest stays simple.

HTML

  • Semantic first. article, header, nav, figure, details, etc. Avoid div soup. This will make it easier to hook into with Quark and CSS.
  • Classes carry static flavor only. details.accordion is fine; .is-open is not. Dynamic state belongs in attributes: use an ARIA or data- attribute if the element does not provide the proper attribute.
  • Ids only where uniqueness is guaranteed. Anything that might be iterated.
  • Use native state before inventing state. [open], :valid, [aria-current], [disabled].
  • Put state on elements, as attributes. If you wish to store data somewhere, put data-* on the nearest sensible ancestor instead.

Quark

  • Match on attributes rather than classes or ids that change. Setting attributes is recommended over toggling / mutating classes and ids, since the latter has a heavier impact on Quark's performance.
  • Write the inverse rule. Rules do not revert when they stop matching. Every state you leave needs a counter-rule (current limitation).
  • One sheet per view, first child of its root. Registers before siblings connect; scope stays tight.
  • Binding identifiers. You may need to target many parts of the document with your Quark rules in order to render content/attributes. If you choose to reserve classes for styling only, use logical, unused attribute patterns for data binding, such as [bind-title] or [bind-field="title"].
  • Prefer nesting and facts over long combinator chains. >, +, ~ and :has() are observed, but a deep chain couples a rule to markup structure; nest rules and select on attributes instead. Avoid over-nesting, since this is also brittle.
  • Namespace variables. $app-theme, $cart-total. Like CSS, bindings are shared per element across Quark sheets.
  • Raise state on the owner. A $variable written on a descendant shadows the owner's; put the @on block on the owner rule (target: for delegation) or call element.quark.setProperty() on the owner from JS. One writer per name per element.
  • preserve while loading. content: $todo.title or preserve; keeps the last good value instead of flashing empty.
  • Never render what you match. A rule that renders children (via content:) that match its own selector re-triggers itself until the loop guard cuts it (50 nested paints, logged as Loop guard: …).
  • Don't let rules gate on each other's writes. [data-a="1"] { data-b: "1"; } next to [data-b="1"] { data-a: "2"; } is a cycle: Quark warns when the sheet builds (rules gate on attributes they write for each other) and the loop guard cuts it at run time if it never settles. Give the transition one attribute with a value, derive the second fact from the first in one direction only, or gate one side on a guard attribute. A rule cannot loop on the attribute it writes itself — that write never re-runs the same declaration.
  • Gate a one-shot on the fact it writes. Rules re-run, and every run restarts a @delay, so an unconditional @delay 3000 { is-open: ""; } reopens a dismissed banner. Write the fact with the effect and gate on it: &:not([data-did-open]) { @delay 3000 { is-open: ""; data-did-open: ""; } }.
  • Do not use variables to smuggle side effects. An unused $x: doThing(); is an element and an event in disguise.
  • Put the fact in the document before branching on it. if() is fine, but many rules testing the same condition (if($user.role == "admin": …)) mean a fact is missing from the State. Write it once as an attribute (data-is-admin: $user.role == "admin";), then select on it: [data-is-admin] button { … }. The condition becomes declarative, addressable by CSS as well as Quark, visible in devtools, and evaluated in one place.
  • Prefer interpolation over concatenation. "Items: #{$n}" and "/api/users/#{$id}", not "Items: " + $n or "/api/users/" + $id. + stays for arithmetic.

Custom JS (Quark Modules)

  • Little to none for simple apps. Elements and Quark rules cover most needs; when they don't, that is usually a missing element or rule, not missing script.
  • Built-in modules first. @use "quark:list", quark:math, quark:string, quark:map, quark:date, quark:url, quark:util cover the derivations views need (sorting, counting, plurals, clamping, dates, query strings); write a module function only for what they lack.
  • Keep functions pure. Values in, a value out. A module holds business logic; bridging the network, storage or the clock is an Adapter's job, and Quark does not await what a function returns. A function may build and return a node it owns without touching the document around it; imperative DOM work Quark cannot yet declare, such as moving focus, is a handle: listener.
  • No build step required. Serve files as-is. Add a bundler only when you have a reason.
  • TypeScript is optional. For a few dozen or hundred lines of pure functions, it usually isn't worth the build process.

Events

  • Use the proper listeners. Use Quark @on or custom elements like <event-handler> to listen to events, since they have a safe teardown procedure. Avoid using raw JS, which does not.
  • Invoke elements with commands. A plain <button type="button" command="--open" commandfor="id"> drives an element with nothing in between; <event-handler command-name target-ref> or a @command in an @on block does the same from any event or relative selector. Put type="button" on command buttons inside forms so they never submit.
  • Wire glue in the Quark sheet. Retargeting, keyboard shortcuts, debouncing and custom payloads are @on options, and @dispatch / @command inside an @on block relay the event onward — <event-handler> offers the same as attributes for markup without a sheet.
  • Delegate. Events bubble; one rule on a common ancestor beats a listener per element — @on click (target: "li[data-id]") names the row and exposes it as target.
  • Cancel default actions with preventDefault(), don't fork the element.
  • Don't expect changing attributes to fire events. State describes; events announce. Keep them distinct.

Building Elements

  • Avoid Shadow DOM ASO applications are heavily data-driven. Shadow DOMs are a hard boundary and severely blunt the power of Quark and CSS. Instead, consider using @scope. The exception is a region handed to a rendering framework that must own its DOM alone (Handing rendering to a framework).
  • Single responsibility. One job, configurable, observable. See Creating Elements.
  • Generic <content-drawer> is good, <add-to-cart> is bad. Business logic belongs to Quark and its modules.
  • Never render children. Composition is the design. The only caveat: an element may render fully author-controlled chilren, such as <include-content> does because this does not hinder composition.
  • Own your region. An element writes only its own attributes and is permitted to write its sub-adapters' attributes. Coordination across elements belongs to Quark sheets. Nothing else writes state.
  • Phrase booleans as assertions. is-loading, did-succeed, has-rendered, should-fetch.
  • Dashed attributes, tag-prefixed events (my-element-change, never change). This is to prevent collisions with native attributes and events - now and in the future.
  • Imperatives are commands, not events. Accept "do this" as a command event with a short --verb (onCommand("--submit")), never as a bubbling my-element-trigger custom event. Commands are addressed to one element and never bubble, so the verb needs no tag prefix.
  • Underscore private members. Minimize unnecessary imperative API surface.
  • Destructure the element in lifecycles. ({ isOpen }) => ({ … }), not (el) => … — a handler that only names the values it reads visibly cannot mutate the element imperatively. Destructured values are snapshots, so an async callback that needs current state should be a defineMethods method (it destructures fresh arguments when it runs).
  • Loosely couple Avoid retaining hard references to other elements. When necessary, use WeakRef or ensure reliable cleanup during the proper lifecycles. Detached nodes make for significant memory leaks.

CSS

  • Heavily utilize state attributes in selectors. provider-fetch[is-loading], li[data-done], [aria-expanded="true"].
  • Modern CSS is encouraged. Nesting, @scope, :has(), container queries, view transitions.
  • Animate with CSS. Declare the target state; let transitions and view transitions do the motion. Reach for Quark's @view-transition only where CSS transitions cannot go — rows that leave, list reflow — and keep until waits short.
  • Valence.css: Prefer roles to classes when borrowing a look. role="button" over class="tag-button".
  • Valence.css: Use tokens (--v-*) so themes and schemes reach your views.

Anti-patterns at a glance

Smell Fix
Toggling .active classes from a handler Set an attribute; let CSS and Quark select on it
A Quark sheet variable that's never read Move the effect to the event that causes it: an @on block with handle: or @dispatch. Consider <event-handler listen-for-lifecycle="connected"> for a connect-time effect.
Two attributes flipped one after another to mean one thing Prefer single attribute with a meaningful value
A custom element that reaches into a sibling or parent Fire an event; let a rule coordinate
A bubbling my-element-trigger event that means "do this" Accept a --verb command (onCommand); invoke it with <button command commandfor>
A *-scroll-to / *-then-do-x attribute on an element Fire the event; compose with <scroll-into-view> or a rule
Shadow DOM "for encapsulation" Light DOM plus scoped Quark / CSS
The same if() condition repeated across rules Derive one attribute (data-is-admin, data-is-eligible) with a rule; select on it from Quark and CSS
"Hello " + $name / "/api/" + $id "Hello #{$name}" / "/api/#{$id}"

Beta. The Nucleus Stack is in beta for a few weeks until features are stabilized and optimized.

Thanks — we'll email you when it ships.

Something went wrong. Please try again.