Explorable makes a document explorable — turning it into a space where
the reader can adjust assumptions, pose alternatives, and watch
conclusions follow in real time.
The library makes this possible without asking much of the author.
Inputs, outputs, and the relationships between them are declared
directly in the document. The library reads those declarations and
takes care of the rest — keeping values current, formatting them
appropriately, showing and hiding content as conditions change, and
making every input something a reader can naturally reach for and
adjust.
Everything is designed to stay out of the way. The library has no
opinions about how a document looks. It handles behaviour and leaves
appearance entirely to the stylesheet. Extensions can be added without
touching the core. And the document itself remains readable as a plain
page even if the library never loads.
Core
This file has one job: to present the library's public face.
It doesn't contain any logic of its own. It simply gathers the pieces
that outside code needs to interact with — the store, the registry,
the engine's initialisation functions — and offers them from a single,
stable location. Everything else can shift and reorganise internally
without that affecting the way the library is consumed from outside.
Base
This is the foundation everything else is built on — the one part of
the library that every other part depends on, and the only place they
all meet.
It carries two responsibilities. The first is memory: a single,
authoritative record of every live value in the document. Inputs write
into it, calculated results flow through it, and anything that wants
to know the current state of the document comes here to ask. Nothing
reads values back out of the visible page — this record is the truth,
and the page is just its reflection.
The second responsibility is the registry: the place where every
extension to the library announces its existence before the document
loads. Every kind of helper, every formatting rule, every interaction
behaviour, every condition operator — all declare themselves here
first. The registry holds those declarations without acting on them,
making them available to the rest of the system when the time comes.
These two things together — shared memory and a shared registry — are
what allow the different parts of the library to remain independent
from one another. They don't need to know about each other. They only
need to know about this.
Engine
The engine is where declared intentions become live behaviour.
By the time the engine starts, every extension has already registered
its capabilities, and every input has a known value. The engine's job
is to wire those things together — to look at every calculated output
in the document, understand what it depends on, and make sure it stays
current whenever any of those dependencies change. From that point on,
the document is alive: adjust an input, and everything that flows from
it updates instantly and in the right order.
That last detail — order — is something the engine handles quietly but
carefully. A document can have outputs that depend on other outputs,
not just on raw inputs. The engine maps these relationships before
anything runs, and ensures that no output ever evaluates before the
values it relies on are already settled. The author never has to think
about this.
The engine also takes responsibility for how calculated values are
presented. A raw number is one thing; a number formatted as a
currency, a percentage, or a date is another. The engine applies those
presentation rules automatically, and signals each output when its
value has changed — giving the rest of the system, and the stylesheet,
the opportunity to respond.
Conditions
The conditions system answers a single question on behalf of any
output element in the document:
should I be visible right now?
Every output that carries a condition is essentially making a claim —
show me when this situation is true — and the conditions system
is what decides, at any moment, whether that claim holds. When it
does, the output becomes active. When it doesn't, it steps back. This
happens continuously and automatically as values in the document
change.
Values enter the document through inputs, flow through the engine's
calculations, and the conditions system sits at the far end of that
journey — watching the resulting state and deciding which conclusions
surface to the reader. It never changes a value. It only listens, and
responds.
Examples
Numeric equality
Simple case. A hidden constant, a range input. Show message only
when the value exactly matches the constant.
Properties
Type
Property
operator
eq
Value:
Numeric range
Show message only when value is between 40 and 60 inclusive.
Properties
Type
Property
operator
and
gte
lte
Value:
Within range (–).
Below minimum ().
Above maximum ().
Either condition met
Two inputs. Show message if either equals its threshold.
Properties
Type
Property
operator
eq
or
helper
alias
A:
B:
At least one value hit its threshold (A=
or B=).
String equality
A select and a hidden string constant.
data-type="string" ensures no numeric coercion
happens.
Properties
Type
Property
operator
eq
neq
helper
alias
What's your name?
Hello,
.
It really is
.
You are not
.
Boolean data-type
A checkbox-like pattern using a select for boolean state, compared
against a boolean constant.
Properties
Type
Property
operator
eq
neq
type
boolean
Are you a member?
Member discount applied.
No discount. Join to save.
Condition on computed output
Condition on a computed output. Total savings must exceed
threshold before the reward message appears.
Properties
Type
Property
operator
gte
lt
helper
alias
multiply
formatter
currency
Weekly saving:
× weeks =
Goal of reached. You saved enough to make it happen.
Not there yet. Keep going.
Conditions Plugin
The core conditions system ships with operators built around numerical
relationships — equal, greater than, lesser than, and so on. This
plugin extends that vocabulary with operators that reason about text:
whether a value contains a certain string, starts with it, or ends
with it.
It is a narrow extension with a clear purpose, and follows the same
pattern as every other plugin: declare, register, and step back.
Examples
Text search
A text input compared against a hidden string constant. Three
operators, each surface their own message independently. All
comparisons are case-insensitive.
Properties
Type
Property
operator
contains
endsWith
startsWith
helper
alias
type
string
Steve Jobs' favourite food must have been
.
Contains
.
Starts with
.
Ends with
.
Formatters Plugin
Formatters are what stand between a raw value and a reader-friendly
representation of it.
A number on its own carries no context. The formatters plugin is what
turns it into a price in a specific currency, a percentage, a nicely
punctuated figure, or a human-readable date — shaped for the language
and conventions of whoever is reading. The locale is picked up
automatically from the document itself, so the same formatter produces
the right result whether the document is in English, German, or
anything else.
Formatters can apply to live, reactive values that update as the
document changes, and equally to static values that are fixed at load
time. In both cases the principle is the same: the raw value is the
truth, and the formatted text is just how it is shown.
Examples
Static formatted values
Fixed values formatted at load time. No inputs, no reactivity —
just raw numbers and dates presented in a form fit for reading.
Properties
Type
Property
formatter
currency
date
number
percentage
? was
Tax Day in the US. For a
?
annual salary in
?,
the estimated federal income tax is approximately
? for
a single filer with no dependents, resulting in a net federal
tax liability of
?
after
(? up
to the wage base) Social Security and
(?)
Medicare deductions.
Helpers Plugin
Helpers are named formulas that outputs can call on to derive their
value from one or more inputs.
The engine ships with a small set of built-in helpers covering basic
arithmetic — adding, subtracting, multiplying, dividing, and passing a
value through unchanged. These handle the common cases. The helpers
plugin exists for everything beyond that: domain-specific calculations
that a particular document needs but that don't belong in the core.
Adding a new helper is a matter of giving it a name and describing
what it does with the values it receives. Once registered, it becomes
available to any output in any document that wants to use it, by name.
Examples
Slug from text inputs
Two text inputs combined and slugified. The result updates as
either value changes, using a configurable separator.
Properties
Type
Property
helper
slugify
Title:
Tag:
Slug:
Visuals Plugin
Visuals are how an input's value can express itself not just as text,
but as appearance.
Where formatters translate a value into a readable string, visuals
translate it into something the stylesheet can act on — a colour, a
state, a signal. The visual itself doesn't decide how anything looks;
it simply communicates the current value to the CSS in a form it can
use. What actually happens on screen is entirely the stylesheet's
decision.
This keeps the boundary clean: the library handles the when and the
what, the stylesheet handles the how.
Examples
Color swatch
A color input whose chosen value is passed to the stylesheet as a
CSS custom property. The label reflects it visually; the library
only communicates the value.
Properties
Type
Property
visual
color
Pick a color:
Main
This is the entry point, the orchestrator, the file that sets
everything in motion.
It has no logic of its own either, but unlike Core its job is sequence
rather than surface. It calls each part of the library to life in the
right order: plugins first, so their capabilities are registered
before anything tries to use them; then the engine, then the
interaction layer. By the time it is done, the document is fully
alive.
Types
This file has no presence at runtime — it never loads in the browser
and plays no part in how the document behaves.
Its sole purpose is to describe the shapes of things: what a formatter
looks like, what a condition expects, what the store offers. These
descriptions exist entirely for the benefit of the author working in
their editor — catching mismatches early, surfacing the right
suggestions, and making the library's intentions legible without
having to read through the implementation.