OVERSCANUI KIT
no button at all a worn bezel with weight channels do not converge zero depth instrumented, full of smoke a field of real characters a camera looking out additive, so no shadow hairlines, no new shader no raster, so no scanlines
import "overscan/radar";

<ov-radar source="contacts" range="800"
          rings="3" period="4"></ov-radar>
import "overscan/scope";

<ov-scope source="square" sweeps="16"
          window="160"></ov-scope>
import "overscan/waterfall";

<ov-waterfall source="square" map="hue"></ov-waterfall>
import "overscan/map";

<ov-map lat="35" lon="139" span="16"
        range="400" source="places"></ov-map>
import "overscan/gauge";

<ov-gauge source="flux" min="0" max="100"
          unit="flux" ticks="5"></ov-gauge>

BUILD INTERFACES THAT LOOK LIKE INSTRUMENTS.

A UI kit for control panels, dashboards and telemetry screens. Web components and a stylesheet, ten dark themes, no build step and nothing to install alongside it. Every readout refuses to invent a number it was never given.

  • 85 COMPONENTS
  • 10 THEMES
  • 76 DEMOS
  • 0 DEPENDENCIES

01src/tokens.css · ten fields

TEN THEMES, AND THEY DIFFER BY MORE THAN COLOUR.

Set one attribute on any container and everything inside it changes: borders, depth, corners, type, motion, and the shader painting the background. The same panel is a genuinely different object in each one, which is why a swap is never a recolour. Every panel below is live.

TERMINAL

white phosphor, flat

reactor
core temp318.4 K
flux0.912
last calnever
aux bus
flux / instrument
monochrome. brightness is the only channel it has
pressure loss

INDUSTRIAL

green phosphor, chunky

REACTOR
CORE TEMP318.4 K
FLUX0.912
LAST CALnever
AUX BUS
FLUX / INSTRUMENT
chunky segments, the over-range cell in alarm
PRESSURE LOSS

CYBER

yellow and cyan, glitched

reactor
core temp318.4 K
flux0.912
last calnever
aux bus
flux / instrument
hairlines, every 8th lit cell on the second accent
pressure loss

ANTISEPTIC

primary on true black, no finish at all

REACTOR
CORE TEMP318.4 K
FLUX0.912
LAST CALnever
AUX BUS
FLUX / INSTRUMENT
does not encode the value. a 2001 readout is a loop, and nobody operates it
PRESSURE LOSS

ESPER

amber and cyan, smoke

REACTOR
CORE TEMP318.4 K
FLUX0.912
LAST CALnever
AUX BUS
FLUX / INSTRUMENT
the reading position marked on the second accent
PRESSURE LOSS

NEO

phosphor green on true black, katakana rain

reactor
core temp318.4 K
flux0.912
last calnever
aux bus
flux / instrument
every fourth lit cell is a leading glyph, drawn brighter
pressure loss

MACHINA

machine vision. red, interlaced, degraded

REACTOR
CORE TEMP318.4 K
FLUX0.912
LAST CALnever
AUX BUS
FLUX / INSTRUMENT
few, large cells. a machine reports coarsely to itself
PRESSURE LOSS

HOLO

near-white, projected, banded and additive

REACTOR
CORE TEMP318.4 K
FLUX0.912
LAST CALnever
AUX BUS
FLUX / INSTRUMENT
thin cells, the reading position marked, everything projected
PRESSURE LOSS

AEGIS

saturated blue on navy, hairlines, very dense

REACTOR
CORE TEMP318.4 K
FLUX0.912
LAST CALnever
AUX BUS
FLUX / INSTRUMENT
the finest division in the kit. density is the register
PRESSURE LOSS

VECTOR

magenta strokes on true black. no raster, so no scanlines

REACTOR
CORE TEMP318.4 K
FLUX0.912
LAST CALnever
AUX BUS
FLUX / INSTRUMENT
few, wide cells with the last one drawn still lit. every cell costs the refresh
PRESSURE LOSS
markup
import 'overscan'
import 'overscan/overscan.css'

<div data-ov-theme="vector">
  <ov-field></ov-field></div>
themes.html →

ALLsrc/*.js · src/*.css

EVERY COMPONENT IN THE KIT.

Everything the kit ships, live and running, before the six sections below pick out the ideas behind them. None of it is a screenshot. The wall is generated from the source and the build fails if it has dropped one, for the same reason the table below tells you it drew 100 of 50,000.

complete
with gaps
cell gauge
NOMINALAUXFAULT
PUMP APUMP BSENSOR 7
PRESSURE LOSS / SECTOR 4
ARMED
CTRLSHIFTPscoped to the panel under the pointer
drive
out318.4
engage
SENSOR
raw counts, no interpretation
READOUT
needs the filter before it can draw
REACTOR ONLINE / SECTOR 4 NOMINALFLUX 0.912 / BUS NOMINAL
pressure loss

sector 4 has been venting for 184 seconds. acknowledging stops the alarm and does not stop the vent.

bus
core temp318.4 K
aux
last calnever
SHOWING 85 OF 85 CUSTOM ELEMENTS, CHECKED AT BUILD TIME

Generated from src/*.js. tools/gen_home.py refuses to write this page if any element defined in the kit is missing from it, so the count above is a measurement rather than a promise. The CSS layer's utility and state classes carry no picture of their own and are not on the wall: .ov-arrive, .ov-finish, .ov-scan, .ov-cluster, .ov-var, .ov-vertical, .ov-has-fault, .ov-has-ornament and .ov-snapguide.

02src/ov-segment.js · REFUSAL.md

READOUTS THAT REFUSE TO GUESS.

Hand a display a value it cannot show and it tells you so, instead of showing something close. There are exactly four reasons a readout is allowed to give, they all draw the same dashes, and the display is not entitled to a fifth opinion.

GIVENDRAWN, AND ITS OWN REASONWHY
nominal seven segments have no letters, and closing the gap would spell a number
99999 five digits into four cells; a display that drops one is inventing the rest
412.0 the instrument was declared 0 to 400, so this is not this instrument's reading
(nothing) no value has ever arrived, which is not the same as a value of zero
markup
import 'overscan'
import 'overscan/overscan.css'

<ov-segment
  value="99999"
  digits="4"
></ov-segment>
demo/segment.html →

03src/gauge.css · src/log.css

GAUGES, LOGS AND MAPS.

Analog instruments that report what they missed. The needle never smooths what it was handed, and the feed says when it dropped lines rather than scrolling them quietly away, because a log that hides its own gaps cannot be audited.

markup
import 'overscan'
import 'overscan/overscan.css'

<ov-gauge
  source="flux"
  unit="flux"
  ticks="5"
></ov-gauge>
demo/instrument.html →

04src/scope.css · src/shaders/scope.glsl

OSCILLOSCOPE AND WATERFALL.

A trace drawn the way a beam actually behaves: brightest where it moves slowest, so it burns on the flat tops and thins out on the fast edges. Stroking a polyline inverts that, and puts the brightest pixels exactly where the beam spent the least time.

markup
import 'overscan'
import 'overscan/overscan.css'

<ov-scope
  source="square"
  sweeps="16"
></ov-scope>
demo/scope.html →

05src/table.css

TABLES BIG ENOUGH TO NEED VIRTUALISING.

Fifty thousand rows, and it says how many of them it actually drew rather than presenting a window as a total. It also states its own ARIA violation on the element, because a kit that hides one is asking you to find it in production.

markup
import 'overscan'
import 'overscan/overscan.css'

<ov-table row-height="21"></ov-table>
demo/data.html →

06src/layout.css · src/window.css

WHOLE SCREENS, NOT PAGES.

Rails, split panes and draggable windows on a shared resize bus, so a pane that changes size tells its instruments instead of each one polling. Nothing here scrolls a document; it allocates a display.

OVERSCAN / SECTOR 4NOMINALAUXSECTOR 4 NOMINAL
INSTRUMENTS
OPTICAL
CONTACTSFEED
PRESSURE LOSS / SECTOR 4CORNERS SAY WHERE YOU ARE
markup
import 'overscan'
import 'overscan/overscan.css'

<ov-window resizable></ov-window>
demo/screen.html →

GETpackage.json · src/overscan.js

TWO WAYS IN, NEITHER OF THEM A BUILD STEP.

Install it from npm and import the elements you use, or point one script tag at the whole kit and write the tags. There is no compile step in either route, and nothing here is a placeholder: the package name, the subpaths and the URL are the real ones.

WITH A BUNDLER

npm install overscan
import "overscan/radar";
import "overscan/overscan.css";

Every element is its own subpath, so you take the ones you use: ov-radar with everything it imports is 13 kB gzip. overscan/radar.js and overscan/ov-radar.js resolve to the same file, for anyone typing what they see in a file listing.

WITH A SCRIPT TAG

<link rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/overscan/src/overscan.css">
<script type="module"
        src="https://cdn.jsdelivr.net/npm/overscan/src/overscan.js"></script>

No install, no bundler, no import map. Not one bare specifier ships in the source, so a browser resolves all 85 elements from that one URL. That is the whole kit, 424 kB of script and 46 kB of CSS gzip, one request per module. Point it at src/ov-radar.js instead to take a single element.

Either way, the markup is the same:

<ov-radar source="contacts" range="800"></ov-radar>
OVERSCAN