Segment Display

segment-displayViewers

Not a font. A segment display is 7, 14 or 16 bars in fixed places, and what makes a real one unmistakable is the bars that are off: a cell showing 1 still shows the ghost of 8, dark red on darker glass. A typeface can only draw the lit state, so this draws the geometry — mitred bars generated from a thickness and a gutter, the thinner diagonals real glass has, sheared about the baseline because every module in a rack is italic by a few degrees. Round that sit the three things a photograph of a panel has and a styled div does not: a pane recessed under the bezel's own overhang, a bloom that carries past each lit bar, and light spilling onto the panel in proportion to how much of the display is lit — 88:88 washes the bezel and 1 barely does. A plain value with a window of digits, or several captioned fields behind one piece of glass, the way a multi-tap delay says DELAY / 4X 60MS / L+R instead of opening a menu. Decimal points ride the cell before them, so 120.5 is four digits wide; overflow is truncated from the end the text is not aligned to, so a tempo drops leading digits and keeps the ones that move.

tempo
prog
masks
4x
60 ms
128.5
Ghost segments
ON

Install

Copies the source into your project. Anything it depends on comes with it.

$ npx shadcn add @syntho-ui/segment-display

Pulls in

segment-fontdisplay-chromecontrol-theme

First time? Add the registry to your components.json once:

components.json
{
  "registries": {
    "@syntho-ui": "https://engine.syntho.app/r/{name}.json"
  }
}

Usage

Controlled or uncontrolled, whichever suits the surrounding state.

example.tsx
import { SegmentDisplay } from "@/components/ui/segment-display"
import { themeVars } from "@/lib/theme"

// A tempo readout: seven segments, right-aligned, the point riding a cell.
<SegmentDisplay type="seven" value="128.5" digits={4} align="right" />

// A whole front panel: three captioned windows behind one piece of glass.
<SegmentDisplay
  size={30}
  fields={[
    { label: "mode", text: "DELAY", digits: 5 },
    { label: "parameter 1", text: "4X 60MS", digits: 7 },
    { label: "parameter 2", text: "L+R", digits: 5 },
  ]}
/>

// No character generator: a bar graph out of the bars themselves.
<SegmentDisplay masks={levels.map((on) => (on ? 0x00ff : 0x00c0))} digits={8} />

// Another decade's panel — the skin is the whole colour surface.
<SegmentDisplay preset="vfd" value="PROGRAM 07" digits={10} />

// Every display under one wrapper, without touching a component.
// Instance color still wins; anything deeper takes a whole preset object.
<div style={themeVars({ displayInk: "#ff9f1a", displaySpill: "#ff9f1a" })}>
  <SegmentDisplay value="PATCH 12" digits={8} />
</div>

Props

PropTypeDefaultDescription
valuestring | numberThe readout. Shown as written — format it first; the display has no opinion about decimal places.
digitsnumberCells in the window. Omit to fit the value. Text longer than the window is truncated from the end it is not aligned to.
align"left" | "right""left"Which end the text sits at, and therefore which end an overflow eats. Numbers default to right.
fieldsSegmentField[]Several windows behind one pane, each with a silkscreen caption above it: { label?, text, digits?, align? }. The captions are laid out from the same widths the windows are, so they stay over their own field at any size.
masksnumber[]Drive the bars directly, one mask per cell, bit 0 being segment A (SEGMENT_NAMES names the bits). Ignores value, and is the counterpart of the dot screen's bitmap: a bar graph out of the middle bars, a rotating busy segment, the annunciators a real panel has. Also available per field.
type"seven" | "fourteen" | "sixteen""fourteen"Seven bars for numbers, fourteen for words, sixteen for the starburst with its split top and bottom bars.
presetDisplayPresetId | DisplayPreset"led-red"Panel skin: led-red, led-amber, led-green, led-blue, vfd, plasma, lcd-green, lcd-blue, lcd-grey. A whole preset object re-skins one instance completely.
colorstringOverride the lit colour. The ghosts are derived from it, so they follow — an amber display with red ghosts is a thing no panel has ever done.
sizenumber26Cell height in pixels. Everything else is proportional to it.
weightnumber0.095Bar thickness as a fraction of the cell height.
slantnumber6Italic lean in degrees, sheared about the baseline. Zero is upright, which reads as a web page rather than a module.
ghostnumberHow visible the unlit bars are, 0..1. Defaults to the skin's. Zero switches them off, which is exactly what a display does not look like.
bloomnumberHalo around the lit bars, 0..1. Defaults to the skin's — zero on the reflective ones.
spillnumberHow far light escapes the window onto the panel, 0..1. Scaled by how much of the display is lit.
brightnessnumber1Overall drive on the lit bars — a dimmed panel, a fade, or an idle state.
labelstringSilkscreen caption above the whole display.
— theme--uikit-display-ink / --uikit-display-spillNot props: the two CSS custom properties (displayInk / displaySpill via themeVars) that re-skin every display under a wrapper. They resolve in CSS, so nothing re-renders. Precedence is instance color, then these, then the skin; anything beyond the light — glass, bezel, bloom, ghost level — is a whole DisplayPreset object rather than more tokens.
framebooleantrueDraw the panel around the pane. Off leaves the bare recessed window to drop into your own.
blinkboolean | numberfalseBlink the lit bars — true for 500 ms, or a period in milliseconds. The ghosts stay put, as they do on hardware.

Interaction

  • Drag with pointer capture, so the gesture keeps tracking past the edge of the control.
  • Shift while dragging for a fine pass; release it mid-gesture and coarse tracking resumes.
  • Double-click resets to defaultValue.
  • Arrow keys step normally, Shift+arrow steps finely, PageUp/PageDown jump by ten, Home/End go to the extremes.
  • Proper slider and switch roles with live aria-valuetext, so a screen reader announces the formatted value rather than the raw number.
  • The scroll wheel adjusts the value while the pointer is over the control; stepped ranges move at least one declared step per notch by default.