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)6 7effect(() => {8    localStorage.setItem('todos', JSON.stringify(todos()))9})10 11const addTodo = () => {12    const text = window.prompt('What needs doing?')?.trim()13 14    if (text) setTodos((prev) => [...prev, { text, done: false }])15}16 17const toggle = (i) =>18    setTodos(todos().map((t, idx) => (idx === i ? { ...t, done: !t.done } : t)))19 20html`21    <button type="button" onclick="${addTodo}">Add</button>22    <ul>23        ${repeat(24            todos,25            (todo, i) => html`26                <li27                    class="${todo.done ? 'done' : ''}"28                    onclick="${() => toggle(i)}"29                >30                    ${todo.text}31                </li>32            `33        )}34    </ul>35`.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', 'type']6 7    type = 'button'8    disabled = false9 10    stylesheet = stylesheet11 12    handleClick = (evt) => {13        evt.stopPropagation()14        this.dispatch('click')15    }16 17    render = () => {18        return html`19            <button ${this.props} class="btn" onclick="${this.handleClick}">20                <slot></slot>21            </button>22        `23    }24}25 26customElements.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 8const renderUser = async () => {9    const user = await loadUser()10    return html`11        <article>12            <h2>${user.name}</h2>13            <p>${user.bio}</p>14        </article>15    `16}17 18html`19    <h1>Profile</h1>20 21    ${suspense(22        renderUser,23        html`<p>Loading profile…</p>`, // fallback24        (err) => html`<p>Failed: ${err.message}</p>` // catch25    )}26`.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<nav>6    <page-link path="/">Home</page-link>7    <page-link path="/about">About</page-link>8    <page-link path="/users">Users</page-link>9</nav>10 11<page-route path="/">12    <h1>Welcome home</h1>13</page-route>14 15<page-route path="/about" src="./pages/about.js"></page-route>16 17<page-route path="/users" exact="false">18    <page-route src="./pages/users.js"></page-route>19    <page-route path="/:userId" src="./pages/user.js"></page-route>20</page-route>21 22<page-route path="/404"> 404 - Page not found! </page-route>23 24<page-redirect path="/404" title="404 - Page not found!"></page-redirect>
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.