v1.18.3 — 7.6KB gzip via CDN

Reactive DOM.
Zero build.

A tiny, web-standards-first templating system that brings reactivity, state, and components to vanilla JavaScript. No bundlers. No JSX. No magic.

7.6KB CDN gzip
0 third-party deps
100% web standards
counter.js
javascript
1import { html, state, effect } from '@beforesemicolon/markup';2 3const [count, updateCount] = state(0);4 5const doubleCount = () => count() * 2;6 7effect(() => {8    console.log(count())9})10 11const countUp = () => updateCount(prev => prev + 1);12const countDown = () => updateCount(prev => prev - 1);13 14const App = html`15  <h1>Conunter</h1>16  <p><strong>Current count</strong>: ${count}</p>17  <p><strong>Double count</strong>: ${doubleCount}</p>18  <button type="button" onclick="${countDown}">-</button>19  <button type="button" onclick="${countUp}">+</button>20`;21 22App.render(document.getElementById('app'));

Built on top of Markup.

Production-ready libraries powered by the same reactive engine — opt-in, modular, and free of third-party runtime dependencies.

Web Components

@beforesemicolon/web-component

A reactive layer over the native Web Components API. Props, state, lifecycles, scoped styles — built on Markup.

Read the docs

Router

@beforesemicolon/router

Declarative routing as web component tags. Nested routes, query matching, lazy-loaded pages — zero JavaScript required.

Read the docs

The platform is the framework.

Web Standards, Web APIs, and modern JavaScript are all you need. Markup just adds the reactivity.

Reactive

Template literals and functions create reactive DOM with state, lifecycles, and side-effects.

Tiny — under 8KB gzip

The CDN browser build transfers at about 7.6KB gzip. Ship enterprise apps without a megabyte of framework.

Web Standards

Three simple APIs that extend the platform you already know. No proprietary abstractions.

Plug & Play

Drop in a script tag and go. No build step, no JSX, no configuration files.

Web Components

Supercharge native Web Components with reactivity. Skip manual DOM manipulation.

Surgical Updates

Data-driven rendering means the DOM updates only where and when it actually needs to.

Looks like HTML. Feels like magic.

Reactive state, component composition, and lifecycle — all from the JavaScript primitives you already know.

EXAMPLE 01
Todos + localStorage
todos.js
javascript
1import { html, state, effect, repeat } from '@beforesemicolon/markup';2 3const [todos, setTodos] = state(4  JSON.parse(localStorage.getItem('todos') ?? '[]')5);6const [draft, setDraft] = state('');7 8// persist on every change9effect(() => {10  localStorage.setItem('todos', JSON.stringify(todos()));11});12 13const addTodo = () => {14  if (!draft().trim()) return;15  setTodos([...todos(), { text: draft(), done: false }]);16  setDraft('');17};18 19const toggle = (i) =>20  setTodos(todos().map((t, idx) =>21    idx === i ? { ...t, done: !t.done } : t22  ));23 24html`25  <input26    value="${draft}"27    oninput="${(e) => setDraft(e.target.value)}"28    placeholder="What needs doing?"29  />30  <button type="button" onclick="${addTodo}">Add</button>31 32  <ul>33    ${repeat(todos, (todo, i) => html`34      <li class="${() => todo.done ? 'done' : ''}"35          onclick="${() => toggle(i)}">36        ${todo.text}37      </li>38    `)}39  </ul>40`.render(document.querySelector('#app'));
EXAMPLE 02
Button component using WebComponent
button-component.js
javascript
1import { WebComponent, html } from '@beforesemicolon/web-component';2import stylesheet from './button.css' with { type: 'css' };3 4class Button extends WebComponent {5  static observedAttributes = ['disabled'];6 7  disabled = false;8 9  stylesheet = stylesheet;10 11  handleClick = (evt) => {12    evt.stopPropagation();13    this.dispatch('click')14  }15 16  render = () => {17    return html`18      <button19        class="btn"20        type="button"21        disabled="${this.props.disabled}"22        onclick="${this.handleClick}">23        <slot></slot>24      </button>25    `;26  }27}28 29customElements.define('bfs-button', Button)
EXAMPLE 03
Suspense (async)
profile.js
javascript
1import { html, suspense } from '@beforesemicolon/markup';2 3const loadUser = async () => {4  const res = await fetch('/api/me');5  return res.json();6};7 8html`9  <h1>Profile</h1>10 11  ${suspense(12    async () => {13      const user = await loadUser();14      return html`15        <article>16          <h2>${user.name}</h2>17          <p>${user.bio}</p>18        </article>19      `;20    },21    html`<p>Loading profile…</p>`,         // fallback22    (err) => html`<p>Failed: ${err.message}</p>` // catch23  )}24`.render(document.querySelector('#app'));
EXAMPLE 04
Page routing
app.html
html
1<!-- in <head>:2<script src="https://unpkg.com/@beforesemicolon/router/dist/client.js"></script>3-->4 5<!-- declarative routing with the markup router -->6<nav>7  <page-link path="/">Home</page-link>8  <page-link path="/about">About</page-link>9  <page-link path="/users/42">User 42</page-link>10</nav>11 12<page-route path="/">13  <h1>Welcome home</h1>14</page-route>15 16<page-route path="/about">17  <h1>About us</h1>18</page-route>19 20<page-route path="/users/:id">21  <user-profile></user-profile>22</page-route>23 24<page-route default>25  <h1>404 — not found</h1>26</page-route>
EXAMPLE 05
Template lifecycles
timer.js
javascript
1import { html, state } from '@beforesemicolon/markup';2 3const [seconds, setSeconds] = state(0);4 5html`6  <p>Elapsed: ${seconds}s</p>7`8  .onMount(() => {9    // runs once when attached to the DOM10    const id = setInterval(() => setSeconds(seconds() + 1), 1000);11    return () => clearInterval(id);12  })13  .onUpdate(() => {14    // runs every time a tracked value changes15    console.log('tick', seconds());16  })17  .render(document.querySelector('#app'));

Install in seconds.

Pick your weapon. Markup works everywhere JavaScript runs.

<script src="https://unpkg.com/@beforesemicolon/markup/dist/client.js"></script>
npm install @beforesemicolon/markup
yarn add @beforesemicolon/markup
pnpm add @beforesemicolon/markup

Build the Web, your way.

Join developers shipping faster with a framework that respects the platform — and your time.