@skirbi/semtic
@skirbi/semtic defines what things are.
It is the authoring layer. It takes clean, readable HTML and produces real semantic HTML output — without classes, without styling opinions, without framework lock-in.
Installation
npm install @skirbi/semtic
Register everything:
import '@skirbi/semtic/register-semtic';
Or selectively:
import { SemticForm, SemticInput } from '@skirbi/semtic';
SemticForm.register();
SemticInput.register();
What it is not
Semtic does not define styling. It does not ship colors, spacing, or typography.
For theming, use @skirbi/pinta.
Structural components
semtic-article
Structured article with header and body.
<semtic-article title="My Post" subtitle="A subtitle">
<p>Content goes here.</p>
</semtic-article>
Attributes: title, subtitle, content-id.
Headers only render when they have content — no empty <header> elements.
semtic-section
Section with optional header generation.
<semtic-section title="Related posts">
...
</semtic-section>
semtic-panel
Semantic container built on <section>. Supports labelledby →
aria-labelledby.
semtic-page
Wraps content in <main>.
semtic-header
Generates a semantic header with <h1> and <h2>.
<semtic-header title="Welcome" subtitle="Sign in to continue"></semtic-header>
semtic-actions
Footer-style container for grouped actions.
semtic-article-meta
Auto-generates semantic metadata.
<semtic-article-meta date="2026-04-13" author="Wesley" readingtime="4"
tags="css,html"></semtic-article-meta>
Attributes: date, readingtime, author, tags.
Forms
All form controls share a unified contract: real HTML controls, label support, required indicator, error slot, attribute forwarding, light DOM only.
semtic-form
Wraps <form>. An optional legend attribute generates a <fieldset>.
semtic-fieldset
Semantic grouping for form controls with description and error handling.
semtic-input
<semtic-input
label="Email"
type="email"
name="email"
required
></semtic-input>
Attributes: label, type, required-label, id, name, placeholder,
value.
semtic-textarea
Textarea equivalent of semtic-input.
semtic-checkbox
Checkbox with unified behavior.
semtic-radio
Radio button with unified behavior.
semtic-select
Select control with full feature parity with HTMLElementSugarSelect.
Static options:
<semtic-select
label="Division"
name="division_id"
required
options='[{"id":1,"name":"A"},{"id":2,"name":"B"}]'
jpath-label="name"
jpath-value="id"
></semtic-select>
Remote options with search:
<semtic-select
label="User"
name="user_id"
searchable
endpoint="/api/users"
param="q"
jpath="data"
jpath-label-template="{name} ({email})"
jpath-value-template="user:{id}"
></semtic-select>
Error slot:
<semtic-select label="Division" required>
<span slot="error">Please pick a division</span>
</semtic-select>
semtic-tooltip
Composable tooltip. Supports long-form nested usage or short attribute-based usage.
Navigation
semtic-nav
Wraps a real <nav>.
<semtic-nav>
<a href="/">Home</a>
<a href="/blog">Blog</a>
</semtic-nav>
semtic-nav-item
Renders as <a> when href is provided, <button> otherwise. Active state via active attribute sets aria-current="page". Nested items are indented.
<semtic-nav-item href="/posts" active>Posts</semtic-nav-item>
<semtic-nav-item href="/series">
Series
<semtic-nav-item href="/series/one">One</semtic-nav-item>
</semtic-nav-item>
semtic-nav-section
Groups nav items with an optional label. Adjacent sections are separated by a border.
<semtic-nav-section label="Main">
<semtic-nav-item href="/dashboard">Dashboard</semtic-nav-item>
</semtic-nav-section>
semtic-sidenav
Sidebar navigation with optional app label. Hidden on mobile, always visible at >= 64rem. Toggle via toggle(), open(), close(), or the semtic-sidenav-open attribute.
<semtic-sidenav label="MyApp">
<semtic-nav-section label="Main">
<semtic-nav-item href="/dashboard">Dashboard</semtic-nav-item>
</semtic-nav-section>
</semtic-sidenav>
semtic-breadcrumb
<semtic-breadcrumb semtic-separator=">">
<a href="/">Home</a>
<a href="/blog">Blog</a>
<span>Article</span>
</semtic-breadcrumb>
Outputs <nav aria-label="Breadcrumb"> with <ol><li> structure. Separator injection is opt-in.
Layout primitives
Structural only. No styling is shipped.
semtic-grid
<semtic-grid semtic-columns="1" semtic-columns-md="2" semtic-columns-lg="3" semtic-gap="4">
<semtic-panel>One</semtic-panel>
<semtic-panel>Two</semtic-panel>
<semtic-panel>Three</semtic-panel>
</semtic-grid>
Attributes: semtic-columns, semtic-columns-sm, semtic-columns-md, semtic-columns-lg, semtic-gap.
semtic-flex
<semtic-flex semtic-justify="end" semtic-gap="2">
<button type="button">Cancel</button>
<button type="submit">Save</button>
</semtic-flex>
Attributes: semtic-direction, semtic-justify, semtic-align, semtic-gap.
semtic-stack
<semtic-stack semtic-gap="4">
<h2>Title</h2>
<p>Text</p>
</semtic-stack>
Attribute: semtic-gap.
Additional components
semtic-terminal
Terminal-style code block with optional title and copy button. Style via semtic-terminal-style="macos|linux|windows|none". Default is linux.
semtic-aside
Callout block with left border accent.
<semtic-aside variant="tip">Use this for pro tips.</semtic-aside>
Variants: note, info, warning, tip.
semtic-table
Data table with static and remote modes.
Static:
<semtic-table
caption="Users"
columns='["Name","Email"]'
data='[{"Name":"Alice","Email":"alice@example.com"}]'
semtic-table-variant="default"
searchable
></semtic-table>
Remote (JSON:API):
<semtic-table
endpoint="/api/users"
jpath="data"
jpath-columns='[
{"label":"Name","jpath":"name","sortable":true},
{"label":"Email","jpath":"email"}
]'
searchable
semtic-table-variant="default"
></semtic-table>
Variants: default (borders, padding, hover rows), zebra (same + striped
rows).
Example: Login page
<html semtic-theme="client1">
<body>
<semtic-grid semtic-columns="1">
<semtic-panel semtic-theme-tone="primary" semtic-theme-variant="solid">
<semtic-stack semtic-gap="4">
<semtic-header
title="Welcome back"
subtitle="Sign in to continue"
></semtic-header>
<semtic-form>
<semtic-stack semtic-gap="3">
<semtic-input label="Email" type="email" name="email" required></semtic-input>
<semtic-input label="Password" type="password" name="password" required></semtic-input>
<semtic-flex semtic-justify="end">
<button type="submit">Login</button>
</semtic-flex>
</semtic-stack>
</semtic-form>
</semtic-stack>
</semtic-panel>
</semtic-grid>
</body>
</html>
Theming
Semtic does not ship styling. Add @skirbi/pinta and apply semtic-theme to
opt in.
<html semtic-theme="mytheme">
<semtic-panel semtic-theme-tone="primary" semtic-theme-variant="solid">
...
</semtic-panel>
</html>