npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@verajs/styles

v0.1.2

Published

Adopts a component's `static styles` for VeraJS: constructed stylesheets into shadow roots, and `@scope`-wrapped hoisting for light DOM.

Readme

@verajs/styles

static styles for VeraJS components (772 B gzip): constructed stylesheets into shadow roots, and @scope-wrapped hoisting for light DOM.

import { wire } from '@verajs/core';
import { renderer } from '@verajs/renderer';
import { styles } from '@verajs/styles';

wire([renderer, styles]);

Once, at your app entry, alongside the renderer. Every component init() adopts its static styles from that point on.

styles is the module; adoptStyles is the function it registers. Wiring that function directly — wire({ on: 'init', fn: adoptStyles, priority: 50 }) — is the same registration written out, and is what to write when you want a priority other than the default 50.

wire comes from @verajs/core, not from @verajs/inserts. A production .min.js inlines the registry into every bundle, so registering through your own copy would write to a map core never reads — working in development and silently doing nothing in production. Taking core's own wire removes the question. Forget the wiring and core says so, once, in development.

Shadow DOM — constructed sheets go to shadowRoot.adoptedStyleSheets; plain strings become a <style data-vm-sheet="styles"> in the shadow root. Both are naturally scoped and safe to re-init.

Light DOM — styles are hoisted to the document once per component class, wrapped in @scope (tag-name) { … } so they apply only inside that component's subtree: scoping without a shadow root, done by the platform. Hoisting also survives renders, since a <style> inside the element would be wiped by the first render pass.

:host works in light DOM too — you write one stylesheet. Inside that @scope block the scoping root is the element, so :host is translated to :scope and :host(.a) to :scope.a when a component has no shadow root. Nothing to remember and nothing to write differently: the same sheet styles the element in both modes, which matters most for a component you installed rather than wrote, since it will use :host and cannot know how you render it. Only SELECTORS are translated — a :host in a value (content: ":host", url(/x/:host.png)) is left exactly as written, as is an escaped identifier like .md\:host.

::slotted() is the exception, and cannot be otherwise. In light DOM the nodes a user slots in are ordinary descendants, so there is no selector that means "assigned to this slot" without marking them — which would put framework attributes in your own markup. It is also the fair one to lose: slotted content is the user's DOM, and page CSS already reaches it there. Development says so if a light component's sheet uses it.

:host-context() is not translated either — Firefox and WebKit never shipped it.

On an engine with no @scope — Safari before 17.4, Firefox before 128 — the block is hoisted unscoped rather than dropped, because a dropped block leaves the component unstyled while an unscoped one still styles it. Every rule then applies page-wide on that engine and only inside the tag everywhere else, so development says so once, by name. Attach a shadow root to scope them on every engine, or write selectors that carry the tag.

Dynamic styles

A sheet is adopted once and never re-read. adoptStyles runs on the init insert — once per element for shadow DOM, and once per component class ever for light DOM. Reassigning MyComponent.styles afterwards changes nothing.

The sheet is also shared by every instance, because static styles is a static member:

a.shadowRoot.adoptedStyleSheets[0] === b.shadowRoot.adoptedStyleSheets[0]   // true

That is what makes constructed sheets cheap — one object, adopted by every instance, parsed once — and it is why the sheet is the wrong place to put anything that varies. Mutating it to restyle one component restyles all of them.

Custom properties are the seam, and they work with no help from this package. var() resolves against the element's inherited custom properties at computed-style time, not when the sheet was adopted, so it re-resolves the moment one changes — and custom properties inherit through the shadow boundary:

import { init, createStore, render, css, html, wire } from '@verajs/core';
import { renderer } from '@verajs/renderer';
import { styles } from '@verajs/styles';

wire([renderer, styles]);

customElements.define(
  'x-tinted',
  class extends HTMLElement {
    static styles = css`p { color: var(--accent, blue); }`;

    connectedCallback() {
      init(this, { mode: 'open' });
      const state = createStore({ accent: 'blue' });
      render(
        () => html`
          <div style="--accent: ${state.accent}">
            <p>tinted</p>
            <button @click=${() => (state.accent = 'red')}>Redden</button>
          </div>
        `
      );
    }
  }
);

document.body.append(document.createElement('x-tinted'));

Clicking the button writes state.accent, which re-renders the binding; the adopted sheet re-resolves var(--accent) against the new value. The sheet itself was never touched.

Setting the property on the host works too, from anywhere — el.style.setProperty('--accent', 'red') — as does inheriting it from an ancestor, and both apply equally to the light-DOM @scope path.

So static styles is deliberately not reactive: it carries the structure, custom properties carry what changes. Verified against a real browser in tests/browser/styles-dynamic.test.js.

applyStyles(styles, element) is exported for manual use — the adoption step alone, for an element whose lifecycle this package's init insert never sees:

import { styles, adoptStyles, applyStyles } from '@verajs/styles';
import { wire } from '@verajs/core';

wire([styles]);                                    // the module — registers adoptStyles on 'init'
wire({ on: 'init', fn: adoptStyles, priority: 50 }); // the same registration, written out

applyStyles(MyPanel.styles, detachedPanel);        // adopt into one element by hand

This lived in @verajs/core until 0.2.0. It moved because most apps do not use static styles and every app was paying for it. If a component declares static styles with this package absent, core warns once in development.

For AI assistants — and anyone who wants the whole API on one page

The repository root's llms.txt is the complete, hand-maintained API reference for every package, written to be pasted into a model's context window: full export tables, the buildless CDN and JSX recipes, semantics that differ from other frameworks, and the mistakes that come up most. Its recipes are executed by the test suite, so they stay honest.