TOGGLE3: 3-state segmented control in the action bar — Dev/Staging/Prod, Light/Dark/Auto
A button is "click → run". A select is "pick one of N". What about "three known states" — because you have Dev/Staging/Prod, or Light/Dark/Auto, or Off/Default/Force-on? That's TOGGLE3 (since v2.13.25) — a 3-state segmented control with its own colour for each state and JS code that fires on state change.
Why a separate type and not select / button?
| Type | State count | UX | Active state memory |
|---|---|---|---|
| BUTTON | 1 (click = run) | Single button | None — fire-and-forget |
| SELECT static | 2-N (dropdown) | List on click | Yes (dataset) |
| TOGGLE3 | Exactly 3 | 3 buttons side-by-side | Yes (index 0/1/2) |
| INPUT / TEXTAREA | Free-text | Text field | Yes (el.value) |
Three states fit perfectly into an "iOS segmented control" pattern — all 3 visible at once, one is active, clicking another swaps active state. No dropdown, no surprises. Classic mobile UX.
First TOGGLE3 action — Light / Dark / Auto
Add a TOGGLE3 action to the action bar, with 3 states:
states[0] = { label: 'Light', value: 'light' }
states[1] = { label: 'Auto', value: 'auto' }
states[2] = { label: 'Dark', value: 'dark' }
defaultStateIdx: 1 // Auto on first visit
In the "Code" field:
// `value`, `stateIdx`, `stateLabel` are injected as const before your code.
const html = document.documentElement;
if (value === 'light') {
html.style.colorScheme = 'light';
html.removeAttribute('data-theme');
} else if (value === 'dark') {
html.style.colorScheme = 'dark';
html.setAttribute('data-theme', 'dark');
} else {
html.style.colorScheme = '';
html.removeAttribute('data-theme');
}
JUSTZIX.log(`Theme → ${stateLabel} (idx ${stateIdx})`);
Click "Dark" → the action changes HTML, click "Light" → code fires again with a new value. Active state has the full colour (from action.color), inactive ones are dimmed (from colorInactiveText).
5 colours — each state gets its own visual identity
Since v2.13.32 TOGGLE3 has 5 configurable colours (more than any other action type):
| Property | What it colours | Default |
|---|---|---|
color | Active state background | Action's default colour |
colorText | Active state text | White |
colorBg | Wrap container background (the whole segment) | Semi-transparent black |
colorHover | Inactive state hover background | filter:brightness off colorBg |
colorInactiveText | Inactive states text | rgba(255,255,255,0.55) |
Use case: Dev/Staging/Prod where each active state has its own semantic colour (green / amber / red), but the wrap background and inactive text stay neutral so they don't mix. A red "Prod" state pops out visually — that's the whole point.
Use case 1 — Environment switcher Dev/Staging/Prod
You have an app with 3 environments under the same paths. Until now: manual URL editing, or bookmarks. TOGGLE3 with code:
// Each state has value = subdomain
states[0] = { label: 'DEV', value: 'dev.app.com' } // color: green
states[1] = { label: 'STG', value: 'staging.app.com' } // color: amber
states[2] = { label: 'PROD', value: 'app.com' } // color: red
// Code:
const newHost = value;
const path = location.pathname + location.search;
location.href = `https://${newHost}${path}`;
Click "PROD" → you jump to production preserving path + query. Memory holds the active state, so after reload you land in the same environment (defaultStateIdx only acts as a fallback on the first entry — memory wins).
Use case 2 — Feature flag tri-state
Your app reads a feature flag from localStorage. Three realistic states: "Off" (force-off), "Default" (server-driven), "Force on".
states[0] = { label: 'OFF', value: 'off' }
states[1] = { label: 'DEFAULT', value: 'default' }
states[2] = { label: 'FORCE', value: 'force' }
// Code:
if (value === 'default') {
localStorage.removeItem('ff_newCheckout');
} else if (value === 'off') {
localStorage.setItem('ff_newCheckout', 'false');
} else {
localStorage.setItem('ff_newCheckout', 'true');
}
location.reload(); // Apply on reload
JUSTZIX.log(`Feature flag → ${stateLabel}`);
QA scenario: in 2 seconds switch a feature flag, verify the view, revert to default. All without devs and without DevTools console.
Use case 3 — Drive from another action via JZ.setValue
You have TOGGLE3 "Theme" (Light/Auto/Dark). And a second BUTTON action "🌙 Night mode" in another bar, which should be a script that sets Dark + hides banners + decreases font-size:
// BUTTON "Night mode" — Code:
JZ.setValue('Theme', 'dark'); // → activates state idx 2 + runs its code
document.body.style.fontSize = '14px';
document.querySelectorAll('.cookie-banner, .promo')
.forEach(el => el.style.display = 'none');
JUSTZIX.log('Night mode activated.');
Works by value — JZ.setValue('Theme', 'dark') finds the state with value='dark'; or by label — JZ.setValue('Theme', 'Dark') case-insensitive; or numerically — JZ.setValue('Theme', 2). All paths converge on the same state.
Pitfalls
- states.length must be EXACTLY 3. The isValidAction() check rejects the action if the array is smaller/larger. Need 2 states? Use SELECT static with 2 options or a BUTTON-toggle. Need 4+? Use SELECT static.
- The value in a state is optional — falls back to
state.label. Recommended: use a short symbolic value (like 'dev'/'stg'/'prod'), because labels may contain spaces or unicode characters. - Memory stores the INDEX (0/1/2), not the value. If you reorder states later — users with prior memory will land on a different state. Best practice: don't reorder states in an existing action.
- defaultStateIdx only applies on first entry. If the user has already used the action, memory wins — even if you change defaultStateIdx in the editor later, existing users will still see their previous state.
- label max 5 chars in the UI editor. The renderer accepts longer values, but the narrow segmented control format demands brevity.
What's next
TOGGLE3 is the action type with the richest "UX identity" — per-state colours, persistence, built-in scope for 3-state decisions. Check also window.JZ helpers for programmatic state control from other actions and DEV/STG/PROD CSS markers as a visual companion for the environment switcher.
Install JustZix — completely free, no server, no account.
Rate this post
No ratings yet — be the first.