Skip to content
design
Instruments

Readout block

The block a readout builds its value, setpoint and advice from.

On this page
Heading, autopilot in command
58
°
Speed over ground, kn
6.2
Depth, m, large digits in a small block
9.6
Distance to Sóller, nmi
14.20
Position, GPS
39°47.6′N 2°41.9′E
Radar range
Standby
True wind, kn, sensor uncalibrated
17.6
Sea temperature, °C, no reading
‒
Three sizes and a larger number size, the enhanced tone, three weights, marker icons, degrees with hinted zeros, reserved widths and alignment, a text position, an off value and both data quality marks, on a live feed.
Code
import {
  ObcTextboxAlignment,
  ObcTextboxFontWeight,
  ObcTextboxSize,
  ReadoutBlock,
  ReadoutDataQuality,
  ReadoutSize,
  ReadoutValueType,
} from "@bosunkit/instruments";
import { MapPin, Navigation, Waves } from "lucide-react";
import { useEffect, useState } from "react";
import type { CSSProperties, Dispatch, ReactElement, ReactNode, SetStateAction } from "react";

export const description: string =
  "A cockpit repeater built from bare blocks on a one-second feed: three sizes and a larger number size, the enhanced tone, three weights, marker icons, degrees with hinted zeros, reserved widths and alignment, a text position, an off value and both data quality marks.";

const CELL_STYLE: CSSProperties = {
  display: "grid",
  gap: 4,
  justifyItems: "end",
  padding: "10px 12px",
  borderRadius: 8,
  background: "var(--container-section-color)",
};
const CAPTION_STYLE: CSSProperties = { fontSize: 11, color: "var(--element-neutral-color)", justifySelf: "start" };

function Cell({ caption, children }: { caption: string; children: ReactNode }): ReactElement {
  return (
    <div style={CELL_STYLE}>
      <span style={CAPTION_STYLE}>{caption}</span>
      {children}
    </div>
  );
}

export default function Showcase(): ReactElement {
  const [step, setStep]: [number, Dispatch<SetStateAction<number>>] = useState<number>(0);

  useEffect((): (() => void) => {
    const timer: number = window.setInterval((): void => {
      setStep((previous: number): number => previous + 1);
    }, 1000);
    return (): void => {
      window.clearInterval(timer);
    };
  }, []);

  const heading: number = 58 + 3 * Math.sin(step / 4);
  const speed: number = 6.2 + 0.4 * Math.sin(step / 3);
  const depth: number = 9.6 + 0.8 * Math.sin(step / 5);
  const distance: number = 14.2 - (step % 60) * 0.02;

  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))",
        gap: 8,
        width: "100%",
        maxWidth: 600,
      }}
    >
      <Cell caption="Heading, autopilot in command">
        <ReadoutBlock
          value={heading}
          size={ReadoutSize.large}
          enhanced
          weight={ObcTextboxFontWeight.bold}
          hasDegree
          maxDigits={3}
          hintedZeros
          hasIcon
          icon={<Navigation size={18} aria-hidden />}
        />
      </Cell>
      <Cell caption="Speed over ground, kn">
        <ReadoutBlock
          value={speed}
          size={ReadoutSize.medium}
          weight={ObcTextboxFontWeight.semibold}
          fractionDigits={1}
          maxDigits={2}
          spaceReserver="00.0"
          alignment={ObcTextboxAlignment.Right}
        />
      </Cell>
      <Cell caption="Depth, m, large digits in a small block">
        <ReadoutBlock
          value={depth}
          size={ReadoutSize.small}
          valueSize={ObcTextboxSize.xl}
          fractionDigits={1}
          hasIcon
          icon={<Waves size={14} aria-hidden />}
        />
      </Cell>
      <Cell caption="Distance to Sóller, nmi">
        <ReadoutBlock
          value={distance}
          fractionDigits={2}
          maxDigits={3}
          hintedZeros
          alignment={ObcTextboxAlignment.Left}
        />
      </Cell>
      <Cell caption="Position, GPS">
        <ReadoutBlock
          value="39°47.6′N 2°41.9′E"
          valueType={ReadoutValueType.text}
          hasIcon
          icon={<MapPin size={14} aria-hidden />}
        />
      </Cell>
      <Cell caption="Radar range">
        <ReadoutBlock value={0} off offText="Standby" size={ReadoutSize.medium} />
      </Cell>
      <Cell caption="True wind, kn, sensor uncalibrated">
        <ReadoutBlock value={17.6} fractionDigits={1} dataQuality={ReadoutDataQuality.lowIntegrity} />
      </Cell>
      <Cell caption="Sea temperature, °C, no reading">
        <ReadoutBlock value={null} dataQuality={ReadoutDataQuality.invalid} />
      </Cell>
    </div>
  );
}

A readout block is one number set on the cap-height grid, with an optional marker icon in front and a degree sign after it. Its variant is the value, a setpoint or an advice, and the variant sets the marker icon and the color. fractionDigits sets the decimals and maxDigits reserves the integer width. off shows offText in place of the number.

Marker icons center on the value even when valueSize differs from the block's size. alignment positions the text inside its reserved width; the parent layout places the whole block.

The readout and the readout list item are built from these blocks. A block is not normally used on its own.

Examples

Value

12.4
The default block, a plain value, at its default size.
Code
import { ReadoutBlock } from "@bosunkit/instruments";
import type { ReactElement } from "react";

export const description: string = "The default block, a plain value, at its default size.";

export default function Value(): ReactElement {
  return (
    <ReadoutBlock value={12.4} fractionDigits={1} />
  );
}

Reserve the value, preserve its meaning

Choose maxDigits for the largest expected integer, then verify negative values, decimals, degree markers and off text at the selected size. alignment positions content inside that reservation; it does not align sibling labels or move the whole block. Use ReadoutList for shared row columns.

fractionDigits is display precision, not a domain-specific rounding policy. Prepare safety-sensitive values through the quantity formatter before rendering. A standalone block needs surrounding label, unit, source and quality context; its visual variant only distinguishes a value, setpoint or advice.

Props

PropTypeDefaultDescription
variantReadoutBlockVariant | undefinedReadoutBlockVariant.valueSemantic variant (value / setpoint / advice).
valuenumber | string | null | undefinednullThe value; null/undefined renders a dash. A number by default, or text when {@link ReadoutBlockProps.valueType} is text.
valueTypeReadoutValueType | undefinedReadoutValueType.numberHow {@link ReadoutBlockProps.value} is interpreted. number (default) formats it via fractionDigits; text renders it verbatim and ignores the numeric format options.
sizeReadoutBlockSize | undefinedReadoutBlockSize.smallDensity tier — icon size, gap, degree tier.
valueSizeObcTextboxSize | undefined–Resolved number-typography size. When unset it is derived from size (small→s, medium→m, large→l), so a parent that de-emphasizes a block (e.g. a secondary setpoint) can pass a smaller size without changing the tier.
enhancedboolean | undefinedfalseAccent (in-command) color tone.
weightObcTextboxFontWeight | undefinedObcTextboxFontWeight.regularNumber font weight (regular / semibold / bold); color is independent.
hasDegreeboolean | undefinedfalseRender the trailing cap-height degree glyph (°).
hasIconboolean | undefinedfalseShow the leading marker-icon container (always on for setpoint/advice).
fractionDigitsnumber | undefined0Number of fraction digits. Available when valueType is number.
maxDigitsnumber | undefined0Integer digits to reserve / hint (independent of fractionDigits). Available when valueType is number.
hintedZerosboolean | undefinedfalseRender muted leading zeros filling the integer part to maxDigits. A negative value keeps them all — the sign is hoisted ahead of them — and an unavailable value renders dashes across the whole reserved width. Available when valueType is number.
hasSignSpacerboolean | undefinedfalseReserve a minus-sign column ahead of the digits: an invisible sign placeholder holds the column open while the value is non-negative and the real sign fills it when negative, so the width does not change across zero. Available when valueType is number.
spaceReserverstring | undefined–Explicit longest string to reserve width for (e.g. "0000.0").
offboolean | undefinedfalseRender offText instead of a number (e.g. equipment powered down).
offTextstring | undefined"OFF"Text shown when off is true.
alignmentObcTextboxAlignment | undefinedObcTextboxAlignment.RightText alignment of the number within its reserved width.
dataQualityReadoutBlockDataQuality | undefined–Per-block measurement quality (outline chip).
alertboolean | AlertFrameConfig | undefinedfalsePer-block alert frame; nests inside any parent alert frame.
touchingboolean | undefinedfalseSetpoint focus (touch) state — only meaningful for the setpoint variant.
hidePhaseReadoutBlockHidePhase | undefinedReadoutBlockHidePhase.noneSetpoint pop-up fade phase — only meaningful for the setpoint variant.
iconReactNode | undefined–Replaces the variant's default marker icon (the upstream icon slot).
classNamestring | undefined–Class name added to the root element.