SM2KB

Browser-native since 2015

The framework that stayed small while the web grew.

Sheriff’s 2KB framework is a compact layer over the DOM: selectors, events, readiness, styling, creation, and convention-driven component extension—without a build step or virtual DOM.

2,362
repository bytes
10
core primitives
0
build tools required
all.js2KB
// browser primitives, made memorable
ready(() => {
  on('.signal', 'click', pulse)
  style(all('.node'), 'opacity', 1)
})

// id · first · all · on · once
// style · create · ready · extends
ready() all() on()

Not a replacement for every application.

A reminder that the browser already ships with a remarkably capable runtime.

Own the code. Keep the payload legible.

01 / EVOLUTION

A decade measured in ideas, not major versions.

The public file history shows concentrated moments of refinement. Each step adds leverage while keeping the same browser-first center.

2015First public roots

A tiny vocabulary for the DOM

The earliest commits establish the direct style: select nodes, wait for readiness, attach behavior, create elements, and move on.

  • Zero compilation
  • Global, memorable helpers
  • Native DOM underneath
id · first · all · style · create
2017The 2KB identity

Events and readiness crystallize

The file announces itself as “Sheriff’s 2KB JS framework” and consolidates its event and startup primitives around a deliberately small API.

  • Queued ready()
  • Collection-aware on()
  • Lightweight A/B hooks
on(el, event, handler)
2018Components without ceremony

Convention-driven extension arrives

Node.extends and NodeList.extends turn ordinary elements into component instances, wiring on* methods to browser events.

  • addEventListener
  • One-shot events
  • Class-name activation
<ul class="extends classList">
2022Small, safer, still direct

The modern snapshot

Startup moves to DOMContentLoaded, component lookup drops dynamic evaluation, and the public API remains readable in one screen.

  • No runtime dependencies
  • No virtual DOM
  • No framework-owned state model
document + this

02 / PAYLOAD

Small enough to understand before your coffee cools.

Uncompressed browser asset or repository-file sizes, pinned to the versions shown. This is a payload perspective—not a feature-equivalence benchmark.

Apt.js 0.1.3minified distribution
2.66 KB
Hyperapp 2.0.22package source
10.03 KB
React 18.3.1production UMD + ReactDOM
139.24 KB
Vue 3.5.40global production build
161.72 KB

03 / LIVE RUNTIME

This interaction runs on the framework.

The button uses ready, first, on, create, and style from the vendored all.js snapshot.

Runtime ready. No signal sent yet.

04 / COMPONENT LAB

Small primitives. Real interface patterns.

Seven dependency-free examples run on the additive 2026 layer. Each preview is live, keyboard-friendly, and paired with the core code that drives it.

Core evolutionframework.v2026.js5.43 KB
HTTP extensionsheriff.http.js< 10 KB

The historical framework.js remains untouched. The 2026 files are additive and browser-native.

01

STATE

Counter

0
const count = Sheriff.signal(0)

count.subscribe(value => {
  output.value = value
})

Sheriff.on(card, 'click', '[data-step]', event => {
  count.update(n => n + Number(event.target.dataset.step))
})
02

INPUT

Slider

Intensity64%
Sheriff.on(range, 'input', event => {
  const value = event.target.value
  output.value = `${value}%`
  orb.style.setProperty('--level', value / 100)
})
03

DISCLOSURE

Accordion

Native events and semantics stay visible, portable, and easy to debug.
Sheriff.component('#faq', ({ listen }) => {
  listen('click', 'button', event => {
    const open = event.target.ariaExpanded === 'true'
    event.target.ariaExpanded = String(!open)
    panel.hidden = open
  })
})
04

NAVIGATION

Tabs

DOM first

Selection, events, rendering, and cleanup stay close to the platform.

Sheriff.on(tablist, 'click', '[role="tab"]', event => {
  tabs.forEach(tab => tab.ariaSelected = String(tab === event.target))
  panels.forEach(panel => {
    panel.hidden = panel.id !== event.target.getAttribute('aria-controls')
  })
})
05

DATA

Sortable table

Small-framework field notes
Status
Signals 5,565 2026 Ready
DOM helpers 2,362 2015 Stable
HTTP cache 9,542 2026 Ready
Sheriff.on(table, 'click', '[data-table-sort]', event => {
  const key = event.target.closest('button').dataset.tableSort
  rows.sort((a, b) => a.dataset[key].localeCompare(b.dataset[key], undefined, { numeric: true }))
  rows.forEach(row => body.append(row))
})
06

VALIDATION

Form

Ready for input.

Sheriff.on(form, 'submit', event => {
  event.preventDefault()
  if (!form.checkValidity()) {
    form.reportValidity()
    return
  }
  const values = Sheriff.formData(form)
})
07

MUTATION

Form submission

Local demo adapter ready.

const result = await api.endpoints.submit.initiate(
  Sheriff.formData(form)
)

status.textContent = `Accepted ${result.data.id}`
08

FETCH EXTENSION · 2026

sheriff.http.js

<10KB

Run the same query twice. The first request reaches the network; the second resolves from the TTL cache.

State
idle
Network
0
Cache hits
0
Entries
0
Waiting for a query.
const api = SheriffHTTP.createApi({
  baseUrl: '/',
  ttl: 30_000,
  endpoints: build => ({
    status: build.query({
      query: () => 'demo-data.json',
      providesTags: ['Status']
    }),
    submit: build.mutation({
      query: body => ({ url: 'submissions', method: 'POST', body }),
      invalidatesTags: ['Status']
    })
  })
})

await api.endpoints.status.initiate()
api.util.invalidateTags(['Status'])

Small is not nostalgia.

Small is a design constraint that keeps paying interest.

Read the original source Compare Apt.js