Design System
Components

Context Bar

The row of selectors that scopes everything below it.

Some screens are meaningless until the user picks an upstream context — a tenant, a route, a service. ContextBar is that row: a small set of required selectors, laid out consistently, with one Clear action that resets them together.

Props

Prop

Type

When to use it

Use a ContextBar when the answer to "what am I looking at?" is a selection the user has to make before any content below is valid — and when that selection then persists across several views.

Do not use it for filters. Filters narrow a result set that is already meaningful; context creates the result set. That difference drives the UI: a context bar sits above the content and outside any collapsible panel, because hiding it would leave the user unable to tell what they are looking at. Filters belong in FilterBar or CollapsibleFilterPanel, which are collapsible for exactly that reason.

Every field is a Combobox

Fields are Combobox props with an added id. A field without onSearch is a searchable static list; one with onSearch defers narrowing to its owner. That is deliberate — a fixed lookup and a server-searched lookup should be visually identical, because the user has no reason to care which is which.

Dependent fields

Context selectors are usually a chain: the second field's options depend on the first. Two rules keep that honest, both visible in the example above:

  1. Disable downstream fields until their parent is set. An enabled selector with no options reads as "there is nothing here", which is a different and wrong message.
  2. Clear a downstream value the new parent can't contain. When the parent changes, check whether the current child value is still valid and null it if not — silently keeping a stale child is how you end up querying for a service that doesn't belong to the selected type.
onChange: (next) => {
  setType(next);
  const stillValid = next ? servicesFor(next).some((s) => s.value === service) : false;
  if (!stillValid) setService(null);
};

Clear

Pass onClear to render the Clear action; omit it and no control renders at all. Clear stays disabled until at least one field has a value, so it never looks actionable when it would do nothing. Clearing is the caller's job — reset every field and whatever the context was driving, so the screen returns to its prompt state rather than showing stale results under an empty bar.

Persisting the selection

Prefer holding the context in the URL rather than in component state. It survives a reload, it can be shared, and in a Next.js Server Component it can be read directly during render with no client state at all:

// /price/manager/seasons?serviceType=Rail__c&serviceId=a0X1
export default async function Page({ searchParams }) {
  const { serviceType, serviceId } = await searchParams;
  // …fetch with the context, render below the bar
}

Accessibility

  • Pass aria-label so the bar is announced as one named group rather than as loose fields.
  • Mark genuinely required fields required; the asterisk is decorative and hidden from assistive technology, and aria-required carries the meaning.
  • Per-field failures belong on the field (error), not on the bar — the user needs to know which lookup failed.

Do / Don't

  • Do keep the bar visible at all times; it is the answer to "what am I looking at?".
  • Do show a prompt below the bar until the context is complete, rather than an empty table.
  • Don't put filters in it.
  • Don't leave a downstream field enabled before its parent is chosen.

On this page