A UI stack you adopt one layer at a time.
Headless state machines, DOM behaviors, layout primitives, motion, icons and 25 accessible components — each published on its own, none requiring the others. Works in Angular, React, Vue, Astro, Svelte, or a plain HTML file.
Landing · Docs · Component demo · Storybook
Most component libraries are all-or-nothing: you want a grid helper, you install a runtime, a theme, and 40 components you'll never render. And Libs splits that into layers you can take individually.
An Angular app that only wants attribute-driven layout installs
@andersseen/layout — pure CSS, no JS runtime, no components. A team building
its own design system takes @andersseen/headless-components and skips every
styled package. Nothing pulls in a sibling unless the docs say so.
┌───────────────────────────────────┐
adapters │ angular · react · vue · astro │ generated wrappers
└────────────────┬──────────────────┘
│
┌────────────────▼──────────────────┐
styled UI │ web-components (Stencil, Shadow │ 25 components
│ DOM, Tailwind + CVA) │
└────────────────┬──────────────────┘
│
┌────────────────▼──────────────────┐
foundation │ headless-components │ pure TS, no DOM
└───────────────────────────────────┘
standalone — nothing above required
┌──────────┬──────────┬───────────┬──────────┬─────────────────────┐
│ layout │ motion │ behaviors │ icon │ vanilla-components │
│ CSS only │ CSS + TS │ DOM only │ 88 SVGs │ zero-dependency CE │
└──────────┴──────────┴───────────┴──────────┴─────────────────────┘
| Package | Version | What it is |
|---|---|---|
@andersseen/web-components |
25 Stencil components, Shadow DOM, themeable | |
@andersseen/headless-components |
State machines + a11y logic, zero styling, no DOM | |
@andersseen/layout |
Flexbox/grid/typography from HTML attributes | |
@andersseen/motion |
Declarative animations, reduced-motion aware | |
@andersseen/behaviors |
Splitters, drag & drop, tooltips, dialogs on plain DOM | |
@andersseen/icon |
88 tree-shakeable SVG icons | |
@andersseen/vanilla-components |
Zero-dependency native Custom Elements | |
@andersseen/angular-components |
Angular standalone wrappers | |
@andersseen/react-components |
React wrappers | |
@andersseen/vue-components |
Vue 3 wrappers | |
@andersseen/astro |
Astro integration | |
@andersseen/mcp |
MCP server — lets AI assistants query the catalog | |
@andersseen/skills |
Installable agent skills, one per library |
Install only what you need:
# The styled components
npm install @andersseen/web-components
# …or just the CSS layout layer, with no JS at all
npm install @andersseen/layoutTheme from a single attribute on <html>, register the elements once, then use
them anywhere:
<html and-color="violet-cyan" and-theme="compact" and-mode="dark">
<head>
<script type="module">
import { defineAllCustomElements } from '@andersseen/web-components';
defineAllCustomElements();
</script>
</head>
<body>
<and-button variant="primary">Ship it</and-button>
</body>
</html>Framework users can skip the manual registration and install the matching adapter — Angular, React, Vue or Astro.
25 components, registering 39 custom elements once sub-parts (and-card-header,
and-tabs-trigger, …) are counted.
Accordion · Alert · Badge · Breadcrumb · Button · Card · Carousel · Code · Context Menu · Control · Drawer · Dropdown · Icon · Input · Menu List · Modal · Navbar · Pagination · Select · Sidebar · Skeleton · Switch · Tabs · Toast · Tooltip
Every one is driven by a headless state machine from
@andersseen/headless-components, so keyboard navigation, focus management and
ARIA wiring are shared rather than reimplemented per component.
Theming — palettes, density themes, light/dark
Components read HSL custom properties. Switch everything from one attribute on
<html>:
<html and-color="violet-cyan" and-theme="compact" and-mode="dark"></html>| Attribute | Values |
|---|---|
and-color |
indigo-rose, slate-amber, emerald-orange, violet-cyan, rose-teal, warm-gold |
and-theme |
default, compact, playful, retro, elegant |
and-mode |
light, dark (class="dark" also works) |
Attributes are namespaced to avoid colliding with application-owned data-*.
The former data-color / data-theme / data-mode selectors still work as
deprecated aliases.
To bake one palette in as the default, import it after the component stylesheet:
@import '@andersseen/web-components/style.css';
@import '@andersseen/web-components/colors/violet-cyan.css';Or override the variables yourself:
:root {
--primary: 243 75% 59%;
--primary-foreground: 0 0% 100%;
--background: 226 30% 98%;
--foreground: 224 71% 4%;
--radius: 0.5rem;
}Layout — flexbox and grid from attributes, no classes
<div and-layout="horizontal gap:md align:center justify:between wrap:wrap">
<div and-layout="vertical gap:sm">
<h2 and-text="h2 weight:bold">Title</h2>
<p and-text="p align:center">Description</p>
</div>
</div>
<!-- Responsive modifiers -->
<div and-layout="grid cols:1 cols@md:2 cols@lg:3 gap:lg">…</div>@andersseen/layout ships compiled CSS only — no JavaScript runtime, no
preflight, safe to drop into an existing app.
Motion — enter, hover and tap animations
<div and-motion="fade-in" and-motion-trigger="enter">Content</div>
<div and-motion="zoom-in" and-motion-trigger="hover">Hover me</div>
<div
and-motion="slide-in-up"
and-motion-trigger="enter"
and-motion-duration="800ms"
and-motion-delay="200ms"
>
Delayed slide
</div>prefers-reduced-motion is honoured automatically.
Behaviors — enhance existing DOM without a framework
Where headless-components is pure state and never touches the DOM,
@andersseen/behaviors attaches interaction directly to elements you already
have.
<div and-splitter="horizontal" and-splitter-default-position="30">
<aside and-splitter-panel="primary">Navigation</aside>
<div and-splitter-handle></div>
<main and-splitter-panel="secondary">Content</main>
</div>
<button and-tooltip="Save changes" and-tooltip-placement="top">Save</button>
<script type="module">
import { defineBehaviors } from '@andersseen/behaviors';
const cleanup = defineBehaviors({ observe: true });
</script>| Attribute | Behavior | Imperative API |
|---|---|---|
and-splitter |
Resizable panel container | createSplitter(container, options) |
and-draggable |
HTML5 drag source | createDraggable(element, config) |
and-drop-zone |
Drop target with optional sorting | createDropZone(element, config) |
and-tooltip |
Hover/focus tooltip | createTooltip(element, options) |
and-dialog-trigger |
Modal or drawer trigger | createDialog(targetElement, options) |
Subpath imports (@andersseen/behaviors/splitter, /tooltip, /dialog,
/drag-drop) keep bundles tight. No production dependencies. Keyboard resizing,
aria-describedby, Escape dismissal and focus trapping are built in — see the
package README for the full reference.
AI tooling — MCP server and agent skills
@andersseen/mcp exposes the component catalog over the Model Context Protocol,
so Claude, Cursor, Copilot and VS Code can query real props and emit
framework-correct snippets instead of guessing.
@andersseen/skills ships installable agent skills — one focused skill per
library, plus an orchestrator.
and-web-components/
├── packages/
│ ├── headless-core/ # @andersseen/headless-components
│ ├── web-components/ # @andersseen/web-components (Stencil)
│ ├── vanilla-components/ # @andersseen/vanilla-components
│ ├── behaviors/ # @andersseen/behaviors
│ ├── layout-core/ # @andersseen/layout (SCSS → CSS)
│ ├── motion-core/ # @andersseen/motion
│ ├── icon-library/ # @andersseen/icon
│ ├── angular-components/ # generated Angular wrappers
│ ├── react-components/ # generated React wrappers
│ ├── vue-components/ # generated Vue wrappers
│ ├── astro/ # @andersseen/astro integration
│ ├── mcp/ # @andersseen/mcp
│ └── skills/ # @andersseen/skills
├── apps/
│ ├── angular-workspace/ # Angular demo app
│ ├── astro-landing/ # Landing page (Playwright e2e)
│ └── docs/ # Documentation site
└── docs/ # Architecture spec, codemap, roadmap
Requires Node 20+ (CI runs 22) and pnpm 10.
git clone https://github.com/Andersseen/and-web-components.git
cd and-web-components
pnpm install
pnpm build:all| Command | What it runs |
|---|---|
pnpm build:all |
Every publishable package, in dependency order |
pnpm build:stencil |
Core chain — required once before Stencil tests |
pnpm start:demo |
Angular showcase app |
pnpm start:astro:dev |
Landing page |
pnpm start:docs:dev |
Documentation site |
pnpm storybook |
Component workbench |
pnpm lint |
Lint every package |
Contributor guide: CONTRIBUTING.md. Architecture, contracts and ADRs live in docs/SSD.md; the task → files map is in docs/CODEMAP.md.
| Surface | URL | Workflow |
|---|---|---|
| Landing | libs.andersseen.dev | deploy-landing.yml |
| Docs | docs.andersseen.dev | deploy-docs.yml |
| Demo | and-web-components-demo.pages.dev | deploy-demo.yml |
| Storybook | and-web-components-storybook.pages.dev | manual — pnpm deploy:storybook |
All four run on Cloudflare Pages. See .github/DEPLOYMENT.md.
Versioning is Changesets-driven and publishing only happens in CI:
pnpm changeset # describe your change
# commit, open a PR, merge to mainMerging to main runs release.yml, which
either opens a chore: version packages PR or — once that PR merges — publishes
the newly-versioned packages. Don't run pnpm release or pnpm publish
locally; it bypasses Changesets and causes version drift.
MIT © Andersseen