← All UI styles

Terminal Phosphor

Green phosphor on black, fixed pitch, with the scan lines still faintly there.

ScanlinetechnicalDeveloper toolsLogs and consolesRetro-computing products

Construction · Scanline

Everything is drawn by a tube: horizontal scan lines across the whole surface, a bloom around anything bright, and edges that fringe slightly.

Every style renders this same screen, so the only thing changing between two of them is the style. The component above is the file below, running.

Prompt

Design this interface in the Terminal Phosphor style. Everything is fixed pitch green on black, aligned to a character grid. Faint scan lines sit over the whole surface and nothing is rounded or shaded.

Build every surface the Scanline way: Everything is drawn by a tube: horizontal scan lines across the whole surface, a bloom around anything bright, and edges that fringe slightly.

Use exactly these values: background #0a0f0a, surfaces #0d140d, text #7dfd8a, secondary text #4faa5c, accent #7dfd8a with #04120a on it; corner radius 0/0/0px; 1px solid borders; no shadows of any kind; ui-monospace for body at 13px, line height 1.45; 4px spacing grid, compact rows; labels in uppercase with wide letter spacing.

Never: proportional typefaces; rounded corners; gradients or soft shadows; more than one hue.

Dark mode uses background #050805, surfaces #0a100a, text #7dfd8a, accent #7dfd8a — the same style, not an inverted one.

The numbers in this prompt are printed from the tokens below, so it cannot drift from what the specimen renders.

What carries the look

  • green phosphor on black
  • fixed pitch character grid
  • faint scan lines
  • no rounded corners

What this style refuses

  • proportional typefaces
  • rounded corners
  • gradients or soft shadows
  • more than one hue

The refusals are the half that keeps generated interfaces from converging — a prompt that only says what to add gets the default look.

Tokens

Palette

RoleLightDark
Background#0a0f0a#050805
Surface#0d140d#0a100a
Border#1e3a1e#1b331b
Text#7dfd8a#7dfd8a
Muted text#4faa5c#4faa5c
Accent#7dfd8a#7dfd8a
On accent#04120a#04120a

Shape and type

Radius0 / 0 / 0 px
Border1px solid
Shadownone
DisplayLucida Console
Bodyui-monospace
Monoui-monospace
Type scale11 · 12 · 13 · 15 · 19 · 26 px
Weights400 / 700 / 700
Line height1.45
Tracking0.01em
Spacing4px grid · compact
Surfacescanline
Accentoutline
Separatorhairline
Labelsuppercase
Blurnone

Measured contrast — body 15:1 light, 15.61:1 dark; secondary text 6.67:1 and 6.94:1. Every pair clears WCAG AA in both themes, checked on each build.

Specimen · Specimen.tsx

import type { CSSProperties } from "react";

/**
 * Vibary · Terminal Phosphor
 *
 * Green phosphor on black, fixed pitch, with the scan lines still faintly there.
 *
 * One complete product screen, rendered from the token object below —
 * the same screen every other Vibary specimen renders, so the style
 * is the only variable. Self-contained: React only, no CSS file, no
 * assets, no chart library.
 *
 * `theme` defaults to following the system setting; pass "light" or
 * "dark" to pin one.
 */

export const TOKENS = {
  "material": "scanline",
  "color": {
    "light": {
      "bg": "#0a0f0a",
      "surface": "#0d140d",
      "border": "#1e3a1e",
      "text": "#7dfd8a",
      "muted": "#4faa5c",
      "accent": "#7dfd8a",
      "accentText": "#04120a"
    },
    "dark": {
      "bg": "#050805",
      "surface": "#0a100a",
      "border": "#1b331b",
      "text": "#7dfd8a",
      "muted": "#4faa5c",
      "accent": "#7dfd8a",
      "accentText": "#04120a"
    }
  },
  "radius": {
    "sm": 0,
    "md": 0,
    "lg": 0,
    "pill": 999
  },
  "border": {
    "width": 1,
    "style": "solid"
  },
  "shadow": {
    "level": "none",
    "value": "none"
  },
  "type": {
    "family": {
      "display": "\"Lucida Console\", \"DejaVu Sans Mono\", Menlo, monospace",
      "body": "ui-monospace, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace",
      "mono": "ui-monospace, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace"
    },
    "scale": [
      11,
      12,
      13,
      15,
      19,
      26
    ],
    "weight": {
      "body": 400,
      "strong": 700,
      "display": 700
    },
    "lineHeight": 1.45,
    "tracking": 0.01
  },
  "space": {
    "unit": 4,
    "density": "compact"
  },
  "effects": {
    "surface": "scanline",
    "accentStyle": "outline",
    "separator": "hairline",
    "labelCase": "upper",
    "blur": 0
  }
};

type Palette = typeof TOKENS.color.light;

export type TerminalPhosphorSpecimenProps = {
  theme?: "system" | "light" | "dark";
  /** Scopes the custom properties; only matters if you render two. */
  scope?: string;
};

function vars(palette: Palette): string {
  return [
    `--sp-bg: ${palette.bg}`,
    `--sp-surface: ${palette.surface}`,
    `--sp-border: ${palette.border}`,
    `--sp-text: ${palette.text}`,
    `--sp-muted: ${palette.muted}`,
    `--sp-accent: ${palette.accent}`,
    `--sp-accent-text: ${palette.accentText}`,
  ].join("; ");
}

/** Fixed content: identical in every specimen, on purpose. */
const NAV: readonly { label: string; path: string }[] = [
  { label: "Overview", path: "M3 9.5 9 4.5l6 5V15a1 1 0 0 1-1 1h-2.5v-4h-5v4H4a1 1 0 0 1-1-1z" },
  { label: "Orders", path: "M4 5h10l-1 9H5zM7 5V3.5h4V5" },
  { label: "Customers", path: "M9 9a2.6 2.6 0 1 0 0-5.2A2.6 2.6 0 0 0 9 9M3.6 15.4c0-2.4 2.4-3.8 5.4-3.8s5.4 1.4 5.4 3.8" },
  { label: "Reports", path: "M4.5 15V8M9 15V4.5M13.5 15v-4.5" },
  { label: "Settings", path: "M9 11.4A2.4 2.4 0 1 0 9 6.6a2.4 2.4 0 0 0 0 4.8M9 2.6v1.4M9 14v1.4M15.4 9H14M4 9H2.6M13.5 4.5l-1 1M5.5 12.5l-1 1M13.5 13.5l-1-1M5.5 5.5l-1-1" },
];

const STATS: readonly { label: string; value: string; delta: string; up: boolean }[] = [
  { label: "Revenue", value: "$48,290", delta: "+12.4%", up: true },
  { label: "Orders", value: "1,284", delta: "+3.1%", up: true },
  { label: "Refunds", value: "42", delta: "-1.8%", up: false },
];

const BARS: readonly { day: string; value: number }[] = [
  { day: "M", value: 0.42 },
  { day: "T", value: 0.61 },
  { day: "W", value: 0.38 },
  { day: "T", value: 0.74 },
  { day: "F", value: 0.93 },
  { day: "S", value: 0.55 },
  { day: "S", value: 0.29 },
];

const ROWS: readonly { name: string; meta: string; state: string; amount: string }[] = [
  { name: "Kyoto Print Co.", meta: "Invoice 2841", state: "Paid", amount: "420.00" },
  { name: "Aurora Supply", meta: "Invoice 2839", state: "Pending", amount: "186.50" },
  { name: "Northwind Studio", meta: "Invoice 2836", state: "Overdue", amount: "678.00" },
];

const SWITCHES: readonly { label: string; on: boolean }[] = [
  { label: "Auto-pay invoices", on: true },
  { label: "Email receipts", on: false },
];

/**
 * The material: how this style paints an element, as opposed to what
 * color it paints it. This is the part that makes Scanline
 * a different design language rather than a different theme —
 * Everything is drawn by a tube: horizontal scan lines across the whole surface, a bloom around anything bright, and edges that fringe slightly.
 */
const MATERIAL = {
  "surface": {
    "background": "repeating-linear-gradient(180deg, color-mix(in srgb, #000000 16%, transparent) 0 1px, transparent 1px 3px), var(--sp-surface)",
    "boxShadow": "inset 0 0 0 1px color-mix(in srgb, var(--sp-accent) 30%, transparent), 0 0 26px -14px var(--sp-accent)",
    "border": "none"
  },
  "button": {
    "background": "transparent",
    "color": "var(--sp-accent)",
    "boxShadow": "none",
    "border": "1px solid var(--sp-accent)"
  },
  "switchOn": {
    "background": "var(--sp-accent)",
    "boxShadow": "0 0 12px -2px var(--sp-accent)",
    "border": "none"
  },
  "switchOff": {
    "background": "transparent",
    "boxShadow": "inset 0 0 0 1px color-mix(in srgb, var(--sp-text) 40%, transparent)",
    "border": "none"
  },
  "knob": {
    "background": "var(--sp-accent-text)",
    "boxShadow": "none"
  },
  "navActive": {
    "background": "color-mix(in srgb, var(--sp-accent) 18%, transparent)",
    "boxShadow": "inset 0 0 0 1px color-mix(in srgb, var(--sp-accent) 40%, transparent)"
  },
  "bar": {
    "background": "var(--sp-accent)",
    "boxShadow": "0 0 12px -3px var(--sp-accent)",
    "border": "none"
  },
  "texture": "repeating-linear-gradient(180deg, color-mix(in srgb, #000000 22%, transparent) 0 1px, transparent 1px 3px)"
};

const SURFACE_BORDER = `${TOKENS.border.width}px solid var(--sp-border)`;
const ROW_SEPARATOR = "1px solid var(--sp-border)";
const RING = 0.68;

export function TerminalPhosphorSpecimen({ theme = "system", scope = "sp-terminal-phosphor" }: TerminalPhosphorSpecimenProps) {
  const css =
    theme === "light"
      ? `.${scope}{${vars(TOKENS.color.light)}}`
      : theme === "dark"
        ? `.${scope}{${vars(TOKENS.color.dark)}}`
        : `.${scope}{${vars(TOKENS.color.light)}}@media(prefers-color-scheme:dark){.${scope}{${vars(TOKENS.color.dark)}}}`;

  const label: CSSProperties = {
    font: `${TOKENS.type.weight.strong} ${TOKENS.type.scale[0]}px ${TOKENS.type.family.body}`,
    letterSpacing: "0.08em",
    textTransform: "uppercase",
    color: "var(--sp-muted)",
  };

  const meta: CSSProperties = {
    font: `${TOKENS.type.weight.body} ${TOKENS.type.scale[0]}px ${TOKENS.type.family.body}`,
    color: "var(--sp-muted)",
  };

  const card: CSSProperties = {
    ...MATERIAL.surface,
    borderRadius: TOKENS.radius.md,
    padding: 12,
  };

  const heading: CSSProperties = {
    font: `${TOKENS.type.weight.strong} ${TOKENS.type.scale[2]}px ${TOKENS.type.family.display}`,
    textTransform: "uppercase",
    letterSpacing: "0.05em",
  };

  const primary: CSSProperties = { ...MATERIAL.button, borderRadius: TOKENS.radius.sm };

  const pill = (active: boolean): CSSProperties => ({
    padding: "2px 8px",
    borderRadius: TOKENS.radius.pill,
    background: active
      ? "var(--sp-accent)"
      : "color-mix(in srgb, var(--sp-text) 10%, transparent)",
    color: active ? "var(--sp-accent-text)" : "var(--sp-muted)",
    font: `${TOKENS.type.weight.strong} ${TOKENS.type.scale[0]}px ${TOKENS.type.family.body}`,
    textTransform: "uppercase",
    letterSpacing: "0.07em",
  });

  return (
    <div
      className={scope}
      style={{
        width: 760,
        display: "flex",
        background: "repeating-linear-gradient(180deg, color-mix(in srgb, var(--sp-text) 8%, transparent) 0 1px, transparent 1px 3px), var(--sp-bg)",
        color: "var(--sp-text)",
        font: `${TOKENS.type.weight.body} ${TOKENS.type.scale[1]}px/${TOKENS.type.lineHeight} ${TOKENS.type.family.body}`,
        letterSpacing: `${TOKENS.type.tracking}em`,
        overflow: "hidden",
        position: "relative",
      }}
    >
      <style>{css}</style>
      <span
        aria-hidden="true"
        style={{
          position: "absolute",
          inset: 0,
          pointerEvents: "none",
          background: MATERIAL.texture,
        }}
      />

      {/* Sidebar */}
      <div
        style={{
          width: 176,
          flex: "none",
          display: "flex",
          flexDirection: "column",
          gap: 4,
          padding: 14,
          borderRight: `${TOKENS.border.width}px solid var(--sp-border)`,
          background: "color-mix(in srgb, var(--sp-surface) 70%, transparent)",
        }}
      >
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }}>
          <span
            style={{
              width: 22,
              height: 22,
              borderRadius: TOKENS.radius.sm,
              background: "var(--sp-accent)",
              display: "grid",
              placeItems: "center",
              color: "var(--sp-accent-text)",
              font: `${TOKENS.type.weight.display} ${TOKENS.type.scale[0]}px ${TOKENS.type.family.display}`,
            }}
          >
            N
          </span>
          <span
            style={{
              font: `${TOKENS.type.weight.display} ${TOKENS.type.scale[2]}px ${TOKENS.type.family.display}`,
              textTransform: "uppercase",
              letterSpacing: "0.06em",
            }}
          >
            Northwind
          </span>
        </div>

        {NAV.map((item, index) => (
          <span
            key={item.label}
            style={{
              display: "flex",
              alignItems: "center",
              gap: 9,
              height: 30,
              padding: "0 9px",
              borderRadius: TOKENS.radius.sm,
              ...(index === 0 ? MATERIAL.navActive : null),
              color: index === 0 ? "var(--sp-accent)" : "var(--sp-muted)",
              font: `${index === 0 ? TOKENS.type.weight.strong : TOKENS.type.weight.body} ${TOKENS.type.scale[1]}px ${TOKENS.type.family.body}`,
              textTransform: "uppercase",
              letterSpacing: "0.07em",
            }}
          >
            <svg width="17" height="17" viewBox="0 0 18 18" fill="none" aria-hidden="true">
              <path
                d={item.path}
                stroke="currentColor"
                strokeWidth={index === 0 ? 1.7 : 1.3}
                strokeLinecap="round"
                strokeLinejoin="round"
              />
            </svg>
            {item.label}
          </span>
        ))}

        <span style={{ flex: 1 }} />

        <span
          style={{
            display: "flex",
            alignItems: "center",
            gap: 8,
            paddingTop: 10,
            borderTop: `${TOKENS.border.width}px solid var(--sp-border)`,
          }}
        >
          <span
            style={{
              width: 26,
              height: 26,
              flex: "none",
              display: "grid",
              placeItems: "center",
              borderRadius: TOKENS.radius.pill,
              background: "color-mix(in srgb, var(--sp-text) 12%, transparent)",
              font: `${TOKENS.type.weight.strong} ${TOKENS.type.scale[0]}px ${TOKENS.type.family.body}`,
            }}
          >
            MK
          </span>
          <span style={{ minWidth: 0 }}>
            <span style={{ display: "block", font: `${TOKENS.type.weight.strong} ${TOKENS.type.scale[0]}px ${TOKENS.type.family.body}` }}>
              Mika Ito
            </span>
            <span style={{ ...meta, display: "block" }}>Finance</span>
          </span>
        </span>
      </div>

      {/* Main */}
      <div
        style={{
          flex: 1,
          minWidth: 0,
          display: "flex",
          flexDirection: "column",
          gap: 10,
          padding: 14,
        }}
      >
        {/* Header */}
        <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
          <span style={{ flex: 1, minWidth: 0 }}>
            <span style={{ ...label, display: "block" }}>March 2026</span>
            <span
              style={{
                display: "block",
                font: `${TOKENS.type.weight.display} ${TOKENS.type.scale[4]}px ${TOKENS.type.family.display}`,
                textTransform: "uppercase",
                letterSpacing: "0.04em",
              }}
            >
              Overview
            </span>
          </span>
          <span
            style={{
              display: "flex",
              alignItems: "center",
              gap: 7,
              padding: "7px 11px",
              background: "var(--sp-surface)",
              border: `${TOKENS.border.width}px solid var(--sp-border)`,
              borderRadius: TOKENS.radius.sm,
              color: "var(--sp-muted)",
              font: `${TOKENS.type.weight.body} ${TOKENS.type.scale[0]}px ${TOKENS.type.family.body}`,
            }}
          >
            <svg width="13" height="13" viewBox="0 0 18 18" fill="none" aria-hidden="true">
              <circle cx="8" cy="8" r="5.4" stroke="currentColor" strokeWidth="1.5" />
              <path d="M12.4 12.4 16 16" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
            </svg>
            Search
          </span>
          <span
            style={{
              ...primary,
              padding: "8px 13px",
              font: `${TOKENS.type.weight.strong} ${TOKENS.type.scale[0]}px ${TOKENS.type.family.body}`,
              textTransform: "uppercase",
              letterSpacing: "0.07em",
            }}
          >
            New report
          </span>
        </div>

        {/* Stats */}
        <div style={{ display: "flex", gap: 10 }}>
          {STATS.map((stat) => (
            <div key={stat.label} style={{ ...card, flex: 1, minWidth: 0 }}>
              <span style={{ ...label, display: "block" }}>{stat.label}</span>
              <span
                style={{
                  display: "block",
                  marginTop: 3,
                  fontVariantNumeric: "tabular-nums",
                  font: `${TOKENS.type.weight.display} ${TOKENS.type.scale[4]}px ${TOKENS.type.family.display}`,
                }}
              >
                {stat.value}
              </span>
              <span style={{ display: "inline-flex", marginTop: 6, ...pill(stat.up) }}>
                {stat.delta}
              </span>
            </div>
          ))}
        </div>

        {/* Chart + ring */}
        <div style={{ display: "flex", gap: 10 }}>
          <div style={{ ...card, flex: 1.6, minWidth: 0 }}>
            <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between" }}>
              <span style={heading}>Revenue by day</span>
              <span style={meta}>7 days</span>
            </div>
            <div
              style={{
                display: "flex",
                alignItems: "flex-end",
                gap: 7,
                height: 78,
                marginTop: 10,
              }}
            >
              {BARS.map((bar, index) => (
                <span key={index} style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 5 }}>
                  <span
                    style={{
                      width: "100%",
                      height: bar.value * 60,
                      borderRadius: `${TOKENS.radius.sm}px ${TOKENS.radius.sm}px 0 0`,
                      ...(index === 4
                        ? MATERIAL.bar
                        : {
                            background: "color-mix(in srgb, var(--sp-accent) 32%, var(--sp-surface))",
                            border: SURFACE_BORDER,
                          }),
                    }}
                  />
                  <span style={meta}>{bar.day}</span>
                </span>
              ))}
            </div>
          </div>

          <div style={{ ...card, flex: 1, minWidth: 0, display: "flex", alignItems: "center", gap: 12 }}>
            <span
              style={{
                width: 74,
                height: 74,
                flex: "none",
                borderRadius: TOKENS.radius.pill,
                display: "grid",
                placeItems: "center",
                background: `conic-gradient(var(--sp-accent) 0turn ${RING}turn, color-mix(in srgb, var(--sp-text) 12%, transparent) ${RING}turn 1turn)`,
              }}
            >
              <span
                style={{
                  width: 52,
                  height: 52,
                  borderRadius: TOKENS.radius.pill,
                  background: "var(--sp-surface)",
                  display: "grid",
                  placeItems: "center",
                  fontVariantNumeric: "tabular-nums",
                  font: `${TOKENS.type.weight.strong} ${TOKENS.type.scale[2]}px ${TOKENS.type.family.mono}`,
                }}
              >
                68%
              </span>
            </span>
            <span style={{ minWidth: 0 }}>
              <span style={{ ...heading, display: "block" }}>Budget</span>
              <span style={{ ...meta, display: "block", marginTop: 4 }}>Spent $34,120</span>
              <span style={{ ...meta, display: "block" }}>Left $16,170</span>
            </span>
          </div>
        </div>

        {/* Table + controls */}
        <div style={{ display: "flex", gap: 10 }}>
          <div style={{ ...card, flex: 1.6, minWidth: 0, padding: 0, overflow: "hidden" }}>
            <div
              style={{
                display: "flex",
                alignItems: "center",
                justifyContent: "space-between",
                padding: `${12}px ${12}px 8px`,
              }}
            >
              <span style={heading}>Recent orders</span>
              <span style={{ color: "var(--sp-accent)", font: `${TOKENS.type.weight.strong} ${TOKENS.type.scale[0]}px ${TOKENS.type.family.body}` }}>
                View all
              </span>
            </div>
            {ROWS.map((row, index) => (
              <div
                key={row.name}
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: 10,
                  height: 38,
                  padding: `0 ${12}px`,
                  borderTop: ROW_SEPARATOR,
                }}
              >
                <span
                  style={{
                    width: 24,
                    height: 24,
                    flex: "none",
                    display: "grid",
                    placeItems: "center",
                    borderRadius: TOKENS.radius.sm,
                    background: "color-mix(in srgb, var(--sp-text) 9%, transparent)",
                    font: `${TOKENS.type.weight.strong} ${TOKENS.type.scale[0]}px ${TOKENS.type.family.mono}`,
                  }}
                >
                  {row.name.slice(0, 1)}
                </span>
                <span style={{ flex: 1, minWidth: 0 }}>
                  <span style={{ display: "block", font: `${TOKENS.type.weight.strong} ${TOKENS.type.scale[1]}px ${TOKENS.type.family.body}` }}>
                    {row.name}
                  </span>
                  <span style={{ ...meta, display: "block" }}>{row.meta}</span>
                </span>
                <span style={pill(index === 0)}>{row.state}</span>
                <span
                  style={{
                    width: 66,
                    textAlign: "right",
                    fontVariantNumeric: "tabular-nums",
                    font: `${TOKENS.type.weight.strong} ${TOKENS.type.scale[1]}px ${TOKENS.type.family.mono}`,
                  }}
                >
                  {row.amount}
                </span>
              </div>
            ))}
          </div>

          <div style={{ ...card, flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 10 }}>
            <span style={heading}>Preferences</span>
            {SWITCHES.map((item) => (
              <span key={item.label} style={{ display: "flex", alignItems: "center", gap: 10 }}>
                <span style={{ flex: 1, minWidth: 0, font: `${TOKENS.type.weight.body} ${TOKENS.type.scale[0]}px ${TOKENS.type.family.body}` }}>
                  {item.label}
                </span>
                <span
                  style={{
                    width: 34,
                    height: 20,
                    flex: "none",
                    position: "relative",
                    borderRadius: TOKENS.radius.pill,
                    ...(item.on ? MATERIAL.switchOn : MATERIAL.switchOff),
                  }}
                >
                  <span
                    style={{
                      position: "absolute",
                      top: 3,
                      left: item.on ? 17 : 3,
                      width: 14,
                      height: 14,
                      borderRadius: TOKENS.radius.pill,
                      ...MATERIAL.knob,
                    }}
                  />
                </span>
              </span>
            ))}
            <span style={{ marginTop: 2 }}>
              <span style={{ display: "flex", justifyContent: "space-between" }}>
                <span style={meta}>Alert threshold</span>
                <span style={{ ...meta, fontVariantNumeric: "tabular-nums" }}>62%</span>
              </span>
              <span
                style={{
                  display: "block",
                  position: "relative",
                  height: 5,
                  marginTop: 7,
                  borderRadius: TOKENS.radius.pill,
                  background: "color-mix(in srgb, var(--sp-text) 14%, transparent)",
                }}
              >
                <span
                  style={{
                    position: "absolute",
                    inset: "0 38% 0 0",
                    borderRadius: TOKENS.radius.pill,
                    background: "var(--sp-accent)",
                  }}
                />
                <span
                  style={{
                    position: "absolute",
                    top: -5,
                    left: "62%",
                    width: 15,
                    height: 15,
                    marginLeft: -7,
                    borderRadius: TOKENS.radius.pill,
                    background: "var(--sp-surface)",
                    border: `2px solid var(--sp-accent)`,
                  }}
                />
              </span>
            </span>
          </div>
        </div>
      </div>
    </div>
  );
}

export default TerminalPhosphorSpecimen;

Nearby styles