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

@skirbi/semtic

v0.0.29

Published

HTML for humans and CSS

Downloads

642

Readme

@skirbi/semtic

Semantic HTML authoring components built on top of @skirbi/sugar.

Semtic is the structure layer of Skirbi. It gives you small, light-DOM Web Components that turn compact authoring markup into real, inspectable HTML.

It aims to be boring:

  • light DOM only
  • real semantic HTML output
  • real form controls where forms are involved
  • custom elements remain in the DOM as CSS hooks
  • attributes are the component API
  • templates define structure
  • JavaScript defines behavior
  • CSS is the primary theming layer
  • no Shadow DOM
  • no styling runtime
  • no hidden utility framework

CSS is where styling belongs. Use @skirbi/pinta for the default Skirbi theme and recipes, @skirbi/dibuho for higher-level composition, or roll your own CSS and target the Semtic elements directly.

Install

npm install @skirbi/semtic

Register all Semtic components:

import '@skirbi/semtic/register-semtic';

Or register only what you use:

import { SemticForm, SemticInput } from '@skirbi/semtic';

SemticForm.register();
SemticInput.register();

Package shape

@skirbi/semtic exports component classes without registering them.

@skirbi/semtic/register-semtic registers the Semtic components as a side-effect import.

All custom elements in this package use the semtic- prefix.

Component-level API details live on the documentation site.

Core mental model

Think of a Semtic component as a one-time compiler.

You author this:

<semtic-article title="Hello world">
  <p>Clean authoring. Real HTML output.</p>
</semtic-article>

On connect, the component renders semantic HTML inside the custom element:

<semtic-article title="Hello world">
  <article>
    <semtic-header>
      <header>
        <h1>Hello world</h1>
      </header>
    </semtic-header>
    <section semtic-body>
      <p>Clean authoring. Real HTML output.</p>
    </section>
  </article>
</semtic-article>

The exact internal shape varies per component, but the contract is stable:

author compactly, output readable HTML, keep everything inspectable.

  • The custom tag remains.
  • Semantic HTML is rendered inside.
  • The rendered DOM becomes the CSS surface.
  • JavaScript runs once and then gets out of the way.

Registration

Every component defines a static tag and registers itself once.

import { HTMLElementSugar } from '@skirbi/sugar';

class SemticExample extends HTMLElementSugar {
  static tag = 'semtic-example';
}

SemticExample.register();

Registration is define-once:

static register() {
  if (!customElements.get(this.tag)) {
    this.init();
    customElements.define(this.tag, this);
  }
}

If another class already registered the same tag, registration is a no-op. This keeps theme-level or app-level overrides possible without requiring a separate registry layer.

Component contract fields

Semtic components expose their authoring contract through static fields.

class SemticFieldset extends HTMLElementSugar {
  static tag = 'semtic-fieldset';
  static exampleHTML = '<semtic-fieldset></semtic-fieldset>';
  static exampleRenderedHTML = '<fieldset><legend semtic-label></legend></fieldset>';
  static renderGuardSelector = ':scope > fieldset';
  static morphTriggerSelector = ':scope > *';
  static attributeDefs = {
    label: { default: '' },
    description: { default: '' },
    'required-label': { default: '*' },
  };
}

Common fields:

| Field | Purpose | | --- | --- | | tag | Custom element name registered with the browser. | | exampleHTML | Minimal authored example used by documentation tooling. | | exampleRenderedHTML | Expected rendered shape used by documentation tooling. | | renderGuardSelector | Selector used to detect that a component already rendered. | | morphTriggerSelector | Selector used to detect authored or morphed content that should render. | | attributeDefs | Attribute API, defaults, and observed attribute source. | | HtmlTemplate | Structural template or template fallback tuple. |

Treat these fields as the component contract. Keep them close to the component and document them with JSDoc comments so generated docs can group them later.

Attribute handling

Use attributeDefs for component-owned attributes.

class SemticExampleHeader extends HTMLElementSugar {
  static tag = 'semtic-example-header';
  static attributeDefs = {
    title: { default: '' },
    subtitle: { default: '' },
  };
}

This gives the component:

  • observedAttributes
  • defaultConfig
  • attributeMap
  • automatic updates through attributeChangedCallback

After calling super.connectedCallback(), component code can rely on the config being initialized:

connectedCallback() {
  super.connectedCallback();

  this.config.title;
  this.config.subtitle;
}

Form controls may also forward unknown host attributes to the real control. That is what makes wire:*, x-*, hx-*, aria-*, data-*, and native form attributes useful without every component explicitly knowing about every tool.

Templates

Templates define structure, not behavior.

They are resolved during .register() by Sugar's template checks.

Strict ID

static HtmlTemplate = 'semtic-example-template';

This looks up <template id="semtic-example-template"> and fails fast if it is missing or invalid.

Use this when the template must exist.

Fallback tuple

static HtmlTemplate = [
  'semtic-example-template',
  () => {
    const t = document.createElement('template');
    t.innerHTML = `
      <article>
        <semtic-header></semtic-header>
        <section semtic-body></section>
      </article>
    `;
    return t;
  }
];

Behavior:

  • If a template with that ID exists, use it.
  • Otherwise, use the fallback.
  • Resolution still happens at register-time.

This is the default Semtic-friendly mode: works by default, but remains overridable by a theme or app.

Direct template or factory

Direct templates and factories are also supported, but they are less common. Prefer the fallback tuple unless there is a clear reason not to.

Rendering pattern

Most Semtic components should follow this shape:

connectedCallback() {
  super.connectedCallback();
  if (this.hasRenderedShape?.()) return;

  const frag = this.renderFromTemplate();

  // Mutate the cloned fragment.
  // Move light-DOM children.
  // Validate required placeholders.

  this.replaceChildren(frag);
}

Rules:

  • Always mutate the cloned fragment.
  • Never mutate the cached template.
  • Prefer replaceChildren() so the custom element remains as the host hook.
  • Use replaceWith() only when intentionally removing the custom element.
  • Fail fast when required placeholders are missing.

Placeholder convention

Use attribute markers, not classes:

<section semtic-body></section>
<h1 semtic-title></h1>

Attribute markers are:

  • classless
  • unlikely to collide with user HTML
  • easy to validate
  • easy to query from the cloned fragment

Use the semtic- prefix for Semtic-owned markers where possible.

Composition

Components can freely compose other components in templates:

<semtic-header></semtic-header>
<semtic-article-meta></semtic-article-meta>

No special upgrade handling is required. If the child component is already defined, it upgrades immediately. If it is defined later, the platform upgrades it then.

Aliases and overrides

Aliases are alternate tag names pointing to the same behavior.

SemticArticle.alias('semtic-post');

Aliases may also set default attributes.

Override behavior only when necessary. Preferred order:

  1. CSS
  2. Template override
  3. Behavior override with a subclass and registration

If subclassing becomes common, the template surface is probably too small.

Styling

Semtic is not the styling layer.

Preferred options:

  1. Use @skirbi/pinta for Skirbi's default tokens and component recipes.
  2. Use @skirbi/dibuho for Skirbi's composition patterns and higher-level UI.
  3. Roll your own CSS and target the Semtic elements directly.
@import '@skirbi/pinta/pinta.css';
@import '@skirbi/dibuho/dibuho.css';
<html skirbi-theme>
  ...
</html>

Local app CSS is fine:

<semtic-panel class="login-card">
  ...
</semtic-panel>
.login-card {
  max-width: 28rem;
  margin-inline: auto;
}

Attributes also work well as local styling hooks:

<semtic-panel login-card>
  ...
</semtic-panel>
semtic-panel[login-card] {
  max-width: 28rem;
}

Semtic should not grow a large utility API. Prefer plain CSS for spacing, layout tuning, composition, and one-off styling.

Layout primitives

Semtic includes a few layout primitives and visual hooks, such as semtic-grid, semtic-flex, semtic-stack, and semtic-divider.

They are intentionally small. They are readable authoring hooks, not a utility framework.

<semtic-grid class="player-details">
  <semtic-panel>Player</semtic-panel>
  <semtic-panel>Stats</semtic-panel>
</semtic-grid>
.player-details {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 1rem;
}

Form controls

Semtic form controls wrap real HTML controls in the light DOM. That keeps them compatible with normal forms and tools such as Livewire, Alpine, HTMX, and plain browser APIs.

Shared behavior:

  • real control elements
  • label support
  • required marker support
  • error content support
  • native input and change events
  • attribute forwarding where appropriate

Example:

<semtic-form method="post" action="/login">
  <semtic-input
    label="Email"
    type="email"
    name="email"
    autocomplete="email"
    required
  ></semtic-input>
</semtic-form>

For detailed attributes and rendered shapes, use the generated JSDoc output.

Livewire

Semtic components are light-DOM components and form controls forward unknown attributes to the real control, so wire:*, x-*, hx-*, aria-*, and data-* attributes can pass through where appropriate.

For Livewire applications using Sugar/Semtic components, import the optional Sugar Livewire integration before registering Semtic:

import '@skirbi/sugar/livewire';
import '@skirbi/semtic/register-semtic';

This helps Livewire compare hydrated trees instead of comparing authored markup to already-rendered component markup.

Documentation

Run JSDoc with:

npm run jsdoc

The source files are the documentation input. Each module should have a file-level @module block and each public component contract field should have a nearby JSDoc comment.

A custom JSDoc template can later group known Semtic static fields into a nicer component contract section. The source should stay simple: no decorators, no TypeScript syntax, and no second documentation DSL unless the default JSDoc data is not enough.

Example: login page

<html skirbi-theme="client">
  <body>
    <semtic-page>
      <semtic-grid class="login-layout">
        <semtic-panel class="login-card">
          <semtic-stack>
            <semtic-header
              title="Welcome back"
              subtitle="Sign in to continue"
            ></semtic-header>

            <semtic-form method="post" action="/login">
              <semtic-stack>
                <semtic-input
                  label="Email"
                  type="email"
                  name="email"
                  autocomplete="email"
                  required
                ></semtic-input>

                <semtic-input
                  label="Password"
                  type="password"
                  name="password"
                  autocomplete="current-password"
                  required
                ></semtic-input>

                <semtic-flex class="login-actions">
                  <button type="submit">Login</button>
                </semtic-flex>
              </semtic-stack>
            </semtic-form>
          </semtic-stack>
        </semtic-panel>
      </semtic-grid>
    </semtic-page>
  </body>
</html>
.login-layout {
  min-height: 100dvh;
  display: grid;
  place-items: center;
}

.login-card {
  width: min(100%, 28rem);
}

.login-actions {
  display: flex;
  justify-content: end;
}

Philosophy

Semtic is:

  • an authoring layer
  • semantic-first
  • minimal
  • framework-independent
  • light-DOM only
  • accessibility-aware
  • designed for normal CSS

Semtic is not:

  • a CSS framework
  • a UI kit
  • a design system
  • a utility-class replacement
  • a substitute for app-specific CSS

Status

Stable for real-world usage. Designed to evolve slowly. YAGNI by default.