v1.18.3

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'));

Extend the way you build.

Use Markup on its own for reactive templates, or add focused companion packages when your app needs custom elements, routing, or localization.

Web Components

@beforesemicolon/web-component

A reactive layer over the native Web Components API. Keep Markup's template model while adding props, state, lifecycles, and scoped styles.

Read the docs

Router

@beforesemicolon/router

Declarative routing as web component tags. Compose pages, nested layouts, query routes, and lazy-loaded views without adopting a framework router.

Read the docs

Intl

@beforesemicolon/intl

Localization for component-first interfaces. Add locale scopes, translated messages, and formatter helpers that fit naturally into Markup-driven UI.

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` <p>Elapsed: ${seconds}s</p> `6    .onMount(() => {7        // runs once when attached to the DOM8        const id = setInterval(() => setSeconds(seconds() + 1), 1000)9        return () => clearInterval(id)10    })11    .onUpdate(() => {12        // runs every time a tracked value changes13        console.log('tick', seconds())14    })15    .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.