Skip to content

Design system

The parts

Every part Signal ships, rendered with real content: 19 sections, 19 primitives, and 73 tokens per mode.

19 sections19 primitives73 tokens2 modes

src/components · src/components/ui · src/theme.css · src/fonts.ts

Tokens

73 variables per mode in src/theme.css: 46 colour and application roles, 24 type roles, and three shape and weight roles.

Surfaces and pairs

  • Aa

    --background

    --foreground

  • Aa

    --card

    --card-foreground

  • Aa

    --primary

    --primary-foreground

  • Aa

    --secondary

    --secondary-foreground

  • Aa

    --muted

    --muted-foreground

  • Aa

    --accent

    --accent-foreground

Elevation pairs

  • sunken

    --surface-sunken

  • raised

    --surface-raised · --shadow-raised

  • overlay

    --surface-overlay · --shadow-overlay

Application states

Selected evidence

--selection · foreground · border

--link · --link-hover

Status families

  • info

    role · soft · border

    solid · foreground

  • positive

    role · soft · border

    solid · foreground

  • warning

    role · soft · border

    solid · foreground

  • critical

    role · soft · border

    solid · foreground

State ramp

  • --primary

  • --primary-hover

  • --primary-active

  • --surface-tint

Type · ratio 1.200

--font-text · IBM Plex Sans

Aa Bb Cc 0123

--font-mono · IBM Plex Mono

Aa Bb Cc 0123

  • d4 · 2.592rem · h1Deploy-aware observability
  • d3 · 2.160rem · section h2Built for how you already debug
  • d2 · 1.800rem · page h2What the tenth minute looks like
  • d1 · 1.500rem · h3Three lines to instrument
  • lead · 1.125remThe section lede, one sentence, twenty words at most.
  • body · 1remRunning prose: traces, logs and deploy events on one timeline.
  • small · 0.875remCard body, nav links, sidebar items, buttons.
  • caption · 0.75remChips, table cells, footnotes, mono chrome.
  • label · caption + 0.08emCoverage

Radius and lines

  • --radius · --radius-inner

    8px outer, 5px inner, never the other way round

  • --border

    one hairline weight, everywhere

  • --ring · --shadow-raised

    focus

    2px ring offset 2 · shadows never use --background

Sections

Each section rendered once, at the width and with the content it ships with.

site-header

src/components/site-header.tsx

Sticky nav: wordmark, five links, a star chip and two buttons. Below md it collapses into a JS-free details menu.

terminal-hero

src/components/terminal-hero.tsx

The home masthead: badge, headline, install row and a live sgl watch transcript that overhangs the container.

v2.4

Every deploy lands on the same timeline as the latency it moved

When p95 moves, the commit that shipped 24 seconds earlier is already next to it.

brew install signalhq/tap/sgl

free for solo devs · no card · self-host the collector

~/payments — sgl watch
$ sgl watch --env prod --since 15m

  prod · us-east-1, eu-west-1 · 3 services

  ok  api-gateway      p95 184ms  err 0.02%  ▁▂▁▁▂▁
  ok  checkout         p95 221ms  err 0.05%  ▁▁▂▁▁▁
  !!  payments-worker  p95 912ms  err 1.31%  ▁▂▄▆██

  ▲ regression  payments-worker · p95 +611ms
    baseline    301ms (24h rolling)
    onset       14:02:11Z · 11m ago
    deploy      8f21c4e "retry budget for webhooks"
                @nadia · 24s before onset

  → sgl trace payments-worker --deploy 8f21c4e
prod · 3 services · 1 regression · refreshed 4s ago
sgl watch · live service health, deploy-annotated

incident-band

src/components/incident-band.tsx

The hero's regression narrative, drawn: a p95 series flat in its 24h envelope, the deploy marker, then the step up 24 seconds later. Inline SVG computed from two point arrays, token-coloured, server-rendered and static.

The regression, drawn: p95 against the deploy timeline

p95 301ms → 912ms at 14:02:11Z · deploy 8f21c4e (@nadia) landed 24s before onset
Open incident evidence

integrations

src/components/integrations.tsx

Eight data sources on a full-bleed hairline grid, one lucide glyph each, describing what the source does.

Signal reads what you already run

No proprietary agent: anything that speaks OTLP works the day you install it, and the rest pipes through sgl ingest.

  • OpenTelemetryOTLP traces + metrics
  • Prometheusscrape + remote write
  • Kubernetespod, node + owner labels
  • PostgreSQLslow query → span link
  • gRPCstreaming span capture
  • Envoysidecar access logs
  • Fluent Bitlog forwarding
  • systemdbare-metal units

feature-grid

src/components/feature-grid.tsx

Four cards carrying nine lines of real YAML, TypeScript and shell, a named outcome, then three supporting claims.

What you get

Built for how you already debug

Signal is a CLI first: everything you can click, you can also review in a pull request.

Deploys are first-class events

Every release lands on the timeline with its sha, author and diff. When a regression starts inside a deploy window, Signal attributes it before you open a dashboard.

.github/workflows/deploy.ymlyaml

# runs after your existing deploy step
- name: Mark deploy in Signal
  uses: signalhq/mark-deploy@v2
  with:
    service: payments-worker
    env: prod
    sha: ${{ github.sha }}
    by: ${{ github.actor }}
    fail-on-regression: true

Three lines to instrument

One import at the top of your entrypoint. The SDK wraps HTTP, gRPC and your database driver, and adds nothing to the request path but a buffer write.

instrument.tsts

import { init } from "@signal/node";

init({
  service: "payments-worker",
  env: process.env.SIGNAL_ENV,
  // all errors, half the slow ones, 1% of the rest
  sample: { errors: 1, slow: 0.5, baseline: 0.01 },
  endpoint: process.env.SIGNAL_ENDPOINT,
});

Alerts live in your repo

Thresholds are code, reviewed in pull requests, versioned with the service they watch. No click-ops, no drift between staging and prod.

signal.config.tsts

import { alert, p95 } from "@signal/config";

export default [
  alert({
    when: p95("checkout.submit").above("400ms"),
    for: "5m",
    notify: ["#oncall-payments", "escalate:tier1"],
  }),
];

Query traces like a table

The same query language in the CLI, the dashboard and the API. Pipe it to jq, commit it as a saved view, or page it into an incident doc.

zsh · sgl querybash
$ sgl query 'p95(latency) by route' \
    --service checkout --since 24h

route                 p95      n       24h
/checkout/submit     412ms   18.2k    +38%
/checkout/quote      121ms   44.9k     -2%
/checkout/receipt     88ms   12.1k     +1%
/checkout/webhook    118ms    6.4k     -1%
/checkout/refund     204ms    2.1k     +4%
“Time to attribution went from forty minutes to under three.”
Priya Raghavan · staff SRE, Kesteven Health · 140 services on the self-hosted collector

Sampling that keeps the weird ones

Head sampling throws away exactly the requests you needed. Signal decides after the fact: every error, every p99 outlier, a slice of the boring remainder.

sgl config sampling

Self-host in one binary

The collector is a single static binary: no queue, no cluster to babysit. On the self-hosted plan raw traces never leave your VPC.

sgl collector verify

A bill you can predict

Priced per ingested gigabyte after sampling, not per host and not per seat. Estimate it against your current volume before you commit.

sgl cost --estimateestimator

pricing-plans

src/components/pricing-plans.tsx

Three plans on one price model, then the definition of a billed gigabyte beside the ledger of things that are never metered.

Plans

Three plans, one meter

Ingested gigabytes after sampling is the only meter on the account.

Free

$0

Up to 5 GB a month after sampling. There is no expiry on it.

  • 30-day retention
  • Unlimited seats and services
  • All seven commands, alerts included
  • No card, no trial clock

Start free

Usage

$0.42/ GB

Every gigabyte after sampling. No minimum, no seat count, no host count.

  • 13-month retention
  • Deploy attribution and trace-linked alerts
  • sgl diff across releases and windows
  • First response in 4 business hours

Estimate your bill

Self-hosted

Aggregates only

The same $0.42 meter, on what the collector forwards. Raw spans never leave your VPC.

  • One static binary, no queue to run
  • Same seven commands, SIGNAL_ENDPOINT set
  • 13-month retention on what you forward
  • sgl collector verify before you cut over

Read sgl collector

What counts as a gigabyte

The collector measures the compressed bytes it accepts, after your sample rates have already thrown work away. A span you never sent is a span you never pay for.

Retention is included rather than metered: thirteen months on the usage plan, thirty days on the free tier. Storing a gigabyte for a year costs the same as storing it for a week.

Run sgl cost --by service to see which service is spending it.

Never on the invoice

seats
invite the whole team, and the contractors
services
one service per deployable, not per licence
hosts
scale to forty replicas at the same price
deploy events
mark every release, including the reverts
alert evaluations
the collector runs them, you do not pay per check
API and CLI calls
query as often as an incident needs

cost-estimator

src/components/cost-estimator.tsx

A client island. Raw volume and sample rate go in, the invoice arithmetic comes out; the default state is the same 107.1 GB and $44.98 the sgl cost transcript prints.

Estimator

Put your own volume in

Every figure below is the arithmetic on your invoice, not a range.

GB before sampling

Whole gigabytes, 0 to 2,000

Sample rate

Sampling is set per service in signal.config.ts. Every error and every p99 outlier is kept whatever the rate, so the slice you drop is the boring remainder.

raw spans
1,071.0 GB / month
sample rate
10%
ingested, billable
107.1 GB
rate
$0.42 / GB

Estimated month

$44.98

107.1 GB at $0.42/GB

Up to 5.0 GB a month after sampling costs nothing. Above that line every gigabyte is billed, including the first five.

Already sending spans? Run sgl cost --estimate and it reads the last thirty days off your own account.

contact-card

src/components/contact-card.tsx

The company surface: four contact routes with stated response times, a full-bleed operations band, and the legal minimum at #legal.

Support

Where to send what

Pick the one that matches the problem and it reaches an engineer, not a queue.

Engineering support

Instrumentation that will not attach, a query that returns nothing, a sample rate that reads wrong. Paste the output of sgl doctor and skip the first three replies.

support@signal.sh4 business hours

Security

Vulnerability reports and coordinated disclosure. Encrypt with the key at fingerprint 3C7A 55E1 90BD 4F26 if the report contains customer data.

security@signal.sh24 hours, any day

Production incidents

Ingest is dropping spans, alerts are not firing, the collector will not start. Usage-plan accounts page an on-call engineer directly.

incidents@signal.sh15 minutes, any hour

Billing

Invoices, VAT numbers, purchase orders and the one question everyone asks: which service spent the gigabytes. Include the invoice number.

billing@signal.sh1 business day

support and billing Mon to Fri, 09:00 to 18:00 CET · incidents and security any hour, any day

How it runs

Data residency
per service
eu-west-1 or us-east-1, chosen at creation and never moved
Retention
13 months
30 days on the free tier, deleted not archived
Sub-processors
2
AWS for compute and storage, Cloudflare for the edge
Ingest uptime
99.95%
measured at the collector, trailing 90 days

changelog-section

src/components/changelog-section.tsx

Dated release entries with icon-marked tags. Shown here in preview: variant='full' swaps the three latest for all eight, adds the migration transcript and the tag filter.

Changelog

Every release is in the log

216 releases since 2024, each written by the person who shipped it.

full changelog
v2.4.0

Incident evidence from deploy to resolution

One incident view now keeps the selected deploy, trace comparison, failure groups, and verification receipt in order.

  • addedIncident detail records baseline and incident UTC windows with collected and expected sample counts.
  • addedTrace comparison keeps missing spans absent and gives every plotted value an exact table row.
  • improvedDeploy evidence records 8f21c4e at 14:01:47Z, 24 seconds before onset.
  • fixedResolved time and separately refreshed current health no longer share one label.
v2.3.2

Sampling budget drift on long-lived workers

Processes that never restart drifted above their configured baseline rate, up to 4x on workers older than a week. Thanks to @tvoss for the reproduction.

  • fixedThe baseline sampler refills its token bucket on the hour instead of on process start.
  • fixedsgl doctor reports the effective sample rate, not the configured one.
v2.3.0

Trace-linked alerts

An alert now arrives carrying the three slowest traces that triggered it, so the notification is the first step of the investigation rather than a reason to go find one.

  • addednotify targets accept templates, including trace_url and deploy_url.
  • addedConditions can hold for up to 24h, for slow-burn error budgets.
  • improvedEvaluation moved into the collector: median notification latency 41s to 6s.

incident-header

src/components/incident-header.tsx

Incident identity and p95 impact. Canonical resolved state plus every status variant from server props.

INC-240806-01Resolved

Webhook retries pushed payments-worker p95 past its rolling baseline

One deploy, two UTC windows, and the trace evidence that isolated the retry loop.

The rollback held through a complete five-minute verification window.

Review deploy diffrefreshed 14:31:09Z · UTC

Incident p95

912 ms

+611 ms

baseline p95
301 ms · 24h rolling
service
payments-worker · prod
onset
14:02:11Z · UTC
24h rolling baseline
2026-08-05 14:01:47 UTC to 2026-08-06 14:01:46 UTC
p95 · ms · 184,240 of 184,240 samples
incident window
2026-08-06 14:02:11 UTC to 2026-08-06 14:18:00 UTC
p95 · ms · 2,410 of 2,437 samples

Current health refreshed 14:31:09Z.

Investigating

Impact is confirmed. The cause is still open.

Identified

The retry budget in 8f21c4e matches the onset window.

Monitoring

The rollback is applied. The verification window is still open.

Resolved

The rollback held through a complete five-minute verification window.

trace-comparison

src/components/trace-comparison.tsx

Aligned trace evidence with exact table equivalence, plus honest missing-span and partial-window variants.

Trace evidence

Baseline against incident

The retry span moved by 959 ms while database and flush time held.

http.server

463 ms → 1,284 ms

webhook.retry

190 ms → 1,149 ms

pg.query

118 ms → 118 ms

signal.flush

2 ms → 2 ms

Dashed baseline, solid incident, one scale to 1,284 ms
Exact span durations for trace 4b21f0c9. The 2 ms flush is drawn above at a 1 px minimum marker.
SpanBaselineIncidentChange
http.server463 ms1,284 ms+821 ms
webhook.retry190 ms1,149 ms+959 ms
pg.query118 ms118 ms+0 ms
signal.flush2 ms2 ms+0 ms

Aligned traces

4 named spans share one 0 to 1,284 ms scale.

Missing span

1 duration is absent and remains missing.

Partial window

2,410 of 2,437 expected traces were complete.

deploy-diff

src/components/deploy-diff.tsx

Exact release configuration before, during, and after the incident, including absent and reverted variants.

Release evidence

The deploy that changed

The selected release raised retry attempts, backoff, and the total budget in one commit.

Retry configuration before, in, and after the selected deploy
FieldBefore8f21c4eRollback
retry.maxAttempts131
retry.backoff0 ms380 ms0 ms
retry.budget250 ms1,200 ms250 ms
retry.scopefailed webhooksfailed webhooksfailed webhooks

Selected deploy

8f21c4e changed attempts, backoff, and retry budget.

No diff

No configuration field changed in the selected release.

Reverted

8f21c4e remains the cause record; 4b9e2d1 restores the prior values.

evidence-timeline

src/components/evidence-timeline.tsx

A semantic ordered list of normalized UTC evidence, with dense and grouped server variants.

UTC sequence

Evidence in order

Times are normalized to UTC. Collector skew was +3.2 seconds before correction.

  1. Deploy01

    Deploy landed

    8f21c4e by @nadia changed the webhook retry budget.

  2. Impact02

    Regression began

    payments-worker p95 left its 24h rolling envelope 24 seconds later.

  3. Evidence03

    Deploy attributed

    The release-window distance ranked 8f21c4e above two older deploys.

  4. Evidence04

    Retry loop identified

    Aligned traces isolated 1,149 ms inside webhook.retry.

  5. Recovery05

    Rollback applied

    4b9e2d1 restored the previous retry budget.

  6. Recovery06

    Incident resolved

    Five complete minutes held p95 at 309 ms and errors at 0.05%.

Dense timeline

Six UTC events remain in one ordered reading sequence.

Grouped timeline

Deploy, diagnosis, and recovery stay grouped without changing order.

failure-cluster

src/components/failure-cluster.tsx

Ranked failure signatures with exact counts and shares, plus empty and uncertain variants.

Failure groups

The retry loop dominates

Complete traces are ranked by matched signature, with counts and shares kept visible.

Aggregation

Count of complete incident-window traces grouped by normalized error signature. Partial traces are excluded.
  1. 1

    Repeated webhook retries

    1,412 · 58.6%

    Three attempts with 380 ms backoff dominate the incident trace.

  2. 2

    Retry budget exhausted

    612 · 25.4%

    The 1,200 ms budget ends before the third attempt returns.

  3. 3

    Downstream timeout

    386 · 16.0%

    The receiver times out after the worker has already retried.

Ranked clusters

Three named groups account for all 2,410 complete traces.

No clusters

No complete traces matched the selected window.

Uncertain ranking

2,410 of 2,437 traces are complete (98.9%), below the 99.5% confidence threshold; 27 partials are excluded.

resolution-receipt

src/components/resolution-receipt.tsx

Rollback, resolved time, verification, and separately refreshed health, plus monitoring and rolled-back variants.

Resolution receipt

Recovery, with receipts

Rollback, verification, and current health are separate facts with separate timestamps.

rollback
4b9e2d1
applied
14:21:10Z UTC
resolved
14:28:11Z UTC
verification
14:23:11Z–14:28:11Z UTC
verified p95
309 ms
verified samples
17,842

Current health: healthy

Health refreshed 14:31:09Z UTC.

Resolved

A complete verification window records the recovery.

Monitoring

The rollback is applied; the verification window remains open.

Rolled back

The rollback is recorded without declaring recovery.

faq

src/components/faq.tsx

Six priced objections, on ui/accordion-disclosure. The first answer is open so the frame photographs both states of the row; the rest are closed, which is how /pricing serves them.

Questions

What the price does not say

Six answers to the questions the estimator raises and the plan cards cannot hold.

What happens when I go over the free tier?

Every ingested gigabyte is billed at $0.42, including the first five. Nothing stops collecting and no service is throttled: the invoice grows and the timeline stays complete.

What does a 10% sample rate drop?

Every error and every p99 outlier is kept whatever the rate you set in signal.config.ts. What a 10% rate drops is the remainder: successful requests inside the normal latency band.

Can I run the collector myself?

Yes, on every plan including the free tier. The collector is one binary and it speaks OTLP, so a service already exporting spans needs one endpoint changed.

How long is trace data kept?

Thirteen months on the usage plan and thirty days on the free tier. Close an account and every span is deleted within thirty days, backups included.

Can I get the raw spans out?

sgl export writes the selected window to newline-delimited JSON, at full fidelity, with no rate limit on the first export of a month.

Is there a contract or a minimum?

Neither. Usage is billed monthly in arrears and the plan can be closed from the CLI on any day of the month.

cta

src/components/cta.tsx

The closing panel: headline, two buttons, an init command and four answers to cost, risk and effort.

Your next regression already has a commit

Install the CLI, point one service at the collector, and the next deploy annotates itself.

sgl init --service payments-worker

  • $0.42 per ingested GB after sampling
  • Free under 5 GB a month, 30-day retention
  • No credit card, no sales call
  • Self-host the collector whenever you want

docs-shell

src/components/docs-shell.tsx

Page-scale: sidebar, article, on-this-page rail and the full-bleed payoff band. Shown here in variant='quickstart'; variant='cli' swaps in the seven-command reference and its own transcript.

This guide takes a Node service from zero to a deploy-annotated latency timeline. It takes about ten minutes, and nothing here is reversible in a way that should worry you — the SDK is a no-op until an ingest key is present.

1

Install the CLI

The CLI is a single binary. It is the same tool your CI uses, so install it the same way in both places.

terminalbash

brew install signalhq/tap/sgl
# or: curl -fsSL https://get.signal.sh | sh

sgl --version
# sgl 2.4.0 (darwin/arm64)
2

Create a service

A service is the unit everything else hangs off: spans, alerts, deploys and cost. Create one per deployable, not per repo.

terminalbash

sgl login
sgl service create payments-worker --env prod

# wrote SIGNAL_INGEST_KEY to .env.local
# scoped to payments-worker/prod
# rotate with: sgl key rotate
3

Instrument it

The SDK wraps HTTP, gRPC and your database driver. It has to load before your application code, which is what the --import flag is for.

instrument.tsts

import { init } from "@signal/node";

init({
  service: "payments-worker",
  env: process.env.SIGNAL_ENV ?? "dev",
});
package.jsonjson

{
  "scripts": {
    "start": "node --import ./instrument.js dist/app.js"
  }
}

Running the collector yourself? Set SIGNAL_ENDPOINT before this step. In self-hosted mode the SDK never opens a connection to signal.sh — the checks to run first are under sgl collector.

4

Mark your deploys

This is the step that makes the rest of Signal worth having. Run it at the end of your deploy job, after traffic shifts.

.ci/deploy.shbash

sgl deploy mark \
  --service payments-worker \
  --env prod \
  --sha "$(git rev-parse HEAD)" \
  --by "$CI_ACTOR"
5

Watch it

Once a deploy and a few thousand spans are in, the timeline can attribute a change to a release. Add --json to the same command to gate a rollout in CI.

terminalbash

sgl watch --env prod --since 15m
sgl watch --env prod --since 15m --json | jq '.regressions'

The first request, returned

A local request proves the middleware emitted a trace before you deploy anything.

~/payments — local request
$ curl -i http://localhost:3000/checkout/health

HTTP/1.1 200 OK
content-type: application/json
{"ok":true,"traceId":"local-7ce91b2a"}
local · 200 OK · 38 ms

One local request, one trace ID: the middleware emitted a span before anything was deployed.

ui/accordion-disclosure

src/components/ui/accordion-disclosure.tsx

The library's disclosure in Signal's recipe: a ruled row, a chevron that turns on the same 250ms clock as the height, and Escape from inside the panel collapsing it onto its summary. The header's burger runs on the same skeleton with its own recipe.

closed

What happens when I go over the free tier?

Every ingested gigabyte is billed at $0.42, including the first five.

Can I run the collector myself?

Yes, on every plan including the free tier.

open

What does a 10% sample rate drop?

Every error and every p99 outlier is kept whatever the rate you set in signal.config.ts. What a 10% rate drops is the remainder: successful requests inside the normal latency band.

How long is trace data kept?

Thirteen months on the usage plan, thirty days on the free tier.

Under prefers-reduced-motion the height tween is off and the panel is simply there. The marker turns on the native [open] attribute rather than on data-state, which the browser writes one task later.

ui/field

src/components/ui/field.tsx

The library’s field shell in Signal’s recipe: one label, one control, one supporting line, and the aria-describedby arithmetic behind it. Precedence is error, then success, then description — a field says one thing at a time, and what it says while wrong is what to do about it. The controls stay native and stay Signal’s.

rest

The value sgl reports under, one per deploy target

Appended to the resolution receipt, plain text

invalid

Sample rate is a percentage. Pick a whole number from 1 to 100.

Both meanings hold: the critical edge and the ring outside it.

valid

Test page delivered to #payments-oncall

read-only

Read-only here. Rotate it with sgl keys rotate.

disabled

Fixed at 7 days on Free; Team sets it per service

submitting

Locked while the key is issued

The control arrives through a render prop, so the id, the aria-invalid and the aria-describedby cannot be dropped without the call site failing to compile — and the critical edge reads the same attribute the reader is announced, so the two cannot disagree. Read-only says so in words rather than with a lock glyph, and disabled steps the border down to the structural hairline: it stopped being a control.

ui/copy-button

src/components/ui/copy-button.tsx

One press, one receipt, and a refusal that says so. The glyph swap runs in three frames at steps(3, jump-none), because a terminal confirms by redrawing.

idle

brew install signalhq/tap/sgl

copied

brew install signalhq/tap/sgl

blocked

sgl init --service payments-worker

disabled

sgl keys show --service payments-worker

navigator.clipboard is undefined outside a secure context and rejects on a denied permission. The control widens and prints clipboard blocked on the critical family — and that is also the button’s accessible name, because the drawn word and the name are one string here. A polite live region says to select the text instead, and the state holds until the next attempt, because a denial is not transient. The fourth row has nothing to copy yet and carries the DOM attribute for it, so it leaves the tab order rather than being dimmed.

Primitives

The ui/ parts every section is built from, in one frame.

ui

src/components/ui/*.tsx

Every theme-authored ui/ part in the states it ships with, including the ones no fiction page uses: disabled and pending actions, a mode-locked switch, a command row on both grounds. The three vendored parts — ui/accordion-disclosure, ui/field, ui/copy-button — have frames of their own above, because a vendored part's capture contract names the states its frame has to carry. ui/container, ui/type and ui/logo are structural: they render in the shell around this page.

ui/button · variant

ui/button · size

ui/button · disabled + pending

Disabled carries the DOM attribute, so it leaves the tab order and the press ramp is gone rather than dimmed. Pending takes the informational family, aria-busy and a label in the progress tense; it does not spin, because the terminal cursor holds this theme’s one ambient slot. Pressed is a toggle, so it lives on the controls that toggle: the theme switch below, the estimator’s sample-rate presets and the changelog’s tag filters all carry aria-pressed.

ui/theme-toggle

The second switch is the mode-locked state: a host that pinned the mode gets a control that says so, not a dead one. aria-pressed still reports the mode showing.

ui/command-row · idle

brew install signalhq/tap/sgl

sgl init --service payments-worker

Two grounds, one row: surface="tint" on the page background, surface="plain" inside a panel that is already tinted. The command scrolls inside its own box at 375 rather than truncating, which is the difference between an instruction and a fragment.

ui/badge · neutral

sgl 2.4.019 sectionssgl trace --deploy 8f21c4e

ui/badge · accent

breakingmigration required

Appearance is variant here, renamed from tone in 2.7.0: tone is the closed semantic set and this theme now has a component that means it.

ui/status-badge

InvestigatingIdentifiedMonitoringResolvedUnclassified

The glyph is a required prop, so a status can never arrive as colour alone. Which incident state earns which tone is the section's knowledge, not the pill's.

ui/icon-chip

Square on the tinted surface for a glyph that illustrates a name; round on a semantic ground for the one that marks a state. Both are aria-hidden: the words are always beside them.

ui/alert-callout

Collection

1,204 of 1,318 expected traces were complete, 91.4%. Partial traces stay excluded from span durations and cluster shares.
Partial window: collected through 14:22 UTC. Evidence is stale after 18:00 UTC.

role="alert" when the tone is critical, role="status" otherwise. The prose stays --foreground; only the label and the glyph carry the family.

ui/state-card

Investigating

Impact is confirmed. The cause is still open.

Monitoring

The rollback is applied. The verification window is still open.

Resolved

The rollback held through a complete verification window.

No diff

No configuration field changed in the selected release.

The box every incident section renders for one of its states. The state name lands on data-state, which is what makes the panes under each section frame addressable.

ui/code-block · card

instrument.tsts

import { init } from "@signal/node";

init({ service: "payments-worker", sample: { errors: 1 } });

ui/code-block · sunken

signal.config.tsts

export default {
  sample: { rate: 0.1, errors: 1, p99: 1 },
};

ui/terminal-window

~/payments — sgl status
$ sgl status --env prod

  ok  api-gateway     p95 184ms
  !!  payments-worker p95 912ms
watching 2 services · 14:22 UTC

ui/section-heading · left

Alerts

Page the person who shipped it

Route by deploy author, not by rota. The commit already knows who to wake.

ui/section-heading · center

Rollout

One service first, then the rest

Instrument the noisiest service, read a week of traces, then widen.

ui/page-hero · tight

Docs

Quickstart

From an uninstrumented Node service to a deploy-annotated latency timeline.

sgl 2.4.0

counterweight slot

Every masthead fills its right half. A1, never the retired A2.

ui/page-hero · standard

Changelog

Every release, with the migration it needs

Breaking changes carry the exact command that moves you across.

counterweight slot

Same slot, more air above and below the stack.

src/components/site-footer.tsx

Three link columns, the product blurb, a build string and a status line. Every link resolves to a page or an anchor the theme ships. Rendered in place as the page shell.