GraphiQL v6#4228
Draft
trevor-scheer wants to merge 101 commits into
Draft
Conversation
## Summary - Swap the vestigial `graphiql-5` reference in `.github/workflows/release.yml` for `graphiql-6` so the changesets-action runs on pushes to the integration branch. - Enter changesets pre-mode with the `alpha` tag so merges aggregate into `6.0.0-alpha.N` prereleases. - Add a changeset that seeds the alpha release line by bumping `graphiql` to v6. No functional change — subsequent alphas accumulate the redesign work. ## Test plan - [ ] On merge: changesets-action opens a "Version Packages (alpha)" PR bumping `graphiql` to `6.0.0-alpha.0`. - [ ] Merging the version PR publishes `graphiql@6.0.0-alpha.0` to npm with the `alpha` dist-tag. Refs: #4219
🦋 Changeset detectedLatest commit: 7f63c9d The changes in this PR will be included in the next version bump. This PR includes changesets to release 8 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
|
The latest changes of this PR are not available as canary, since there are no linked |
## Summary - Introduce a new `packages/graphiql-react/src/style/tokens.css` with the v6 OKLCH-based design token system. Both dark and light palettes ship together. - Light theme activates explicitly via `data-theme="light"` or automatically via `prefers-color-scheme: light` when no theme is pinned. Dark remains the default. - Existing v5 HSL variables are unchanged; nothing in `@graphiql/react` references the new tokens yet. - Future PRs will restyle components to consume the new tokens and shim the v5 variables. Refs: #4219
## Summary Storybook gives us a fast feedback loop for iterating on the look of the app and individual components — flipping themes/density/font-size without spinning up the full GraphiQL shell. - Bootstrap Storybook 10 in `@graphiql/react`. Stories colocated as `<component>.stories.tsx`; ships one starter (`Spinner`) to validate the pipeline. - A global decorator wraps every story in `.graphiql-container` with `data-theme` / `data-density` / `data-font-size` attributes, toggleable from the Storybook toolbar. - Move `Uri`, `KeyMod`, `KeyCode`, and `Range` out of the `utility` barrel into direct imports from `utility/monaco-ssr`. The barrel was bundling two unrelated concerns — lightweight UI helpers (`cn`, `pick`, etc.) and heavy Monaco re-exports — so any story reaching for `cn` transitively pulled Monaco's ESM bundle, which doesn't initialize cleanly inside Storybook's preview iframe. Splitting them keeps UI primitives lightweight. ## Run locally From the repo root: ``` yarn storybook # dev server on http://localhost:6006 yarn build-storybook # static build under packages/graphiql-react/storybook-static ``` Refs: #4219
## Summary Component a11y is covered by Storybook + axe; this is the full-app counterpart. `cypress-axe` runs axe at four checkpoints during a normal session (initial render, after running a query, with the docs panel open, with the history panel open) and gates PRs against a committed baseline. `cypress/.a11y-baseline.json` pins today's accepted violations — color-contrast in several spots, a couple of nested-interactive cases, link-in-text-block in the docs panel. CI fails on net-new only. The spec lives alongside the existing Cypress suite, so it runs as part of the normal `yarn e2e` flow. `cypress.config.ts` gets a small `writeBaseline` Node task so the spec can persist baseline updates from inside the browser. ## Refresh baseline ``` yarn workspace graphiql test:a11y:update ``` Refs: #4219
) ## Summary Component-level a11y for v6. `@storybook/addon-a11y` surfaces axe results next to each story while you're working on it; `@storybook/addon-vitest` folds those same checks into the existing Vitest suite so they run as part of `yarn test` in CI. The model is per-story `parameters.a11y.test`: - `'error'` (default) — axe violations fail the test - `'todo'` — warn only, for stories with known issues we plan to fix - `'off'` — skip a11y for the story `vitest.config.mts` is split into two projects: - `unit` — existing jsdom suite, unchanged behavior - `storybook` — Vitest browser mode (Playwright Chromium), picks up `.stories.*` files The PR CI workflow gets one new step: `yarn playwright install --with-deps chromium` ahead of `yarn test`. ## Run locally ``` yarn workspace @graphiql/react test # both projects yarn workspace @graphiql/react vitest run --project=unit # unit only yarn workspace @graphiql/react vitest run --project=storybook ``` The Storybook a11y panel surfaces the same axe results live during `yarn workspace @graphiql/react storybook`. Refs: #4219
## Summary - Migrate `Button`, `UnStyledButton`, `ToolbarButton`, and `ExecuteButton` CSS to v6 OKLCH tokens. - Add `variant?: 'default' | 'primary'` to `Button`; `primary` renders the Run-button style. - Switch `:focus` to `:focus-visible` on interactive states so the focus ring no longer fires on mouse click. Aligns with [MDN `:focus-visible`](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible) and WCAG 2.4.7 (Focus Visible). - Import `clsx` directly in `button` and `toolbar-button`, following the leaf-module pattern from #4272. A follow-up PR will convert remaining callers and remove the `cn` re-export. - Add Storybook stories: `Primitives/Button` (Default, Primary, Success, Error, Disabled) and `Primitives/ToolbarButton` (Default). ## Test plan - [x] Open Storybook `Primitives/Button` and verify each variant matches the design. - [x] Tab into the buttons, then mouse-click them. Focus ring appears on Tab only, not on click. - [x] Open `Primitives/ToolbarButton`. Hover the icon button; a v6 tooltip appears. - [x] Run `yarn dev:graphiql`. The Run button and toolbar buttons match the new design. Refs: #4219
…nal (#4404) ## Summary - The bottom status bar drops the plugin count, the `UTF-8` encoding label, and the `GraphQL` language label. None of them told the user anything useful; they just made the bar look like a code editor's chrome. - The connection dot reflects real state instead of just "schema loaded or not." It's amber while introspection or a request is in flight, green once a schema is loaded, red when the last introspection or request failed, and gray when nothing has happened yet. Each state has a matching text label and an accessible title on the dot. - The types count stays as a plain readout ("142 types") once a schema is loaded. It's deliberately not clickable, so the status bar doesn't reach into the doc-explorer plugin. - This doesn't cover full subscription-level status (an open subscription's live/closed state, tracked separately from a one-shot query). The dot derives from schema-load state plus the last request's fetch/error state, which handles the common cases without new transport plumbing. ## Test plan - [x] Open GraphiQL. The status bar shows only a connection dot with a label and a types count, with no plugin count, `UTF-8`, or `GraphQL`. - [x] Before a schema loads, the dot is gray and reads "Idle". - [x] While the schema is loading, the dot is amber and reads "Connecting". - [x] Once the schema loads, the dot turns green, reads "Connected", and the types count appears. - [x] Run a query successfully. The dot stays green. - [ ] Point the endpoint at something that errors (bad URL, malformed response). The dot turns red and reads "Connection error". - [ ] `yarn workspace @graphiql/react test` passes. Refs: #4219
## Summary - The glyph inside a `KeycapHint` key (`⌘`, `⌃`, `⌥`, `⇧`, `⏎`, letters) sat slightly above center. Centering the line box wasn't enough by itself, since these glyphs carry more ink above the baseline than below it. - The glyph now renders in its own span with a small `translateY` nudge, measured against each glyph's actual ink bounds rather than guessed. `⌃` needed the most help; the rest were already close. ## Test plan - [ ] Open GraphiQL, look at a keycap hint in the top bar (e.g. the `⌘` `⏎` run shortcut), and confirm the glyph is vertically centered on the key. - [ ] In Storybook, view `Primitives/KeycapHint` (Single, ChordWithModifier, RunShortcut stories) in both dark and light theme and confirm centering holds across the different glyphs. Refs: #4219
…4401) ## Summary The Settings dialog's "Clear data" button was unstyled, and clicking it turned the whole button green. This restyles it to match the segmented controls it sits beside — same typography (`font-size-small`, weight 500, `letter-spacing-ui`), `bg-subtle` fill + `border-muted` border, and the same height as a full segmented bar (4px vertical padding = a segment cell's 3px plus the track's own 1px). It no longer reads as a foreign element in that row. On click, the label cross-fades to a green checkmark **in place** and back after ~1.5s — rather than the check appearing beside the label. The label stays in the DOM (at `opacity: 0`) so the button keeps a fixed width and its accessible name, and an `sr-only` live region announces "Data cleared" for screen-reader users. The swap respects `prefers-reduced-motion`. ## Test plan - [x] Open Settings. The "Clear data" button should read as a sibling of the Density/Font size segmented controls — same font, weight, and height — not a larger, heavier button. - [x] Click "Clear data". The label should swap to a green checkmark in place (no sideways shift, no reflow), then revert to "Clear data" after ~1.5s. - [x] Confirm the stored settings (theme, density, font size, etc.) are actually cleared after clicking. - [x] Check both Dark and Light themes — the button and checkmark should look right in each. - [x] With a screen reader, clicking should announce "Data cleared". Refs: #4219
#4399) ## Summary Reworks the green **"Run"** button in the top bar (`.graphiql-top-bar-run`) so it stops reading as a heavy, cluttered element in an otherwise monochrome bar. - Add a small play icon before the "Run" label. - Add a faint vertical divider between the label and the keyboard shortcut. - Recolor the shortcut keys (`⌘` `⏎`) into translucent-white chips that sit on the green fill, instead of the default dark-bordered keycap boxes that looked like foreign objects on the button. - Set the label to medium weight (500) rather than the previous heavy bold (600). Also removes the hardcoded `font-weight: 600` from the shared primary button variant (`.graphiql-button-primary`), used by the collections plugin dialogs (Import / Export / Save), so its label matches the other buttons. ## Test plan - [ ] Open GraphiQL and look at the top bar "Run" button: play icon + "Run" + a faint divider + the `⌘⏎` shortcut as subtle chips. It should read as one calm unit, not stand out as bold/busy. - [ ] The shortcut chips should be legible white-on-green, not dark boxes. - [x] Open a collections dialog (e.g. Save / Import) and confirm the green primary button's label matches a neutral button's weight. - [x] Disabled state (e.g. GET selected with a mutation) still dims the whole button and shows the "switch to POST" tooltip. Refs: #4219
…4402) ## Summary Schema search was unreachable. The top-bar "Jump to schema" button had no `onClick`, and the real shortcut (`⌘⌥K`) clicked a `.graphiql-sidebar` button selector that stopped existing once v6 renamed that container to `.graphiql-activity-rail`. The dead button is gone, and schema search is rebound to plain `⌘K` / `Ctrl+K`. The handler opens the doc explorer through `setVisiblePlugin` instead of clicking a DOM node, and it takes priority over monaco-editor's own `Cmd+K` binding, so it works whether or not an editor pane has focus. The shortcuts help dialog and key map are updated to match. ## Test plan - [x] Press `⌘K` (or `Ctrl+K`) with no editor focused. The doc explorer opens and the search input is focused. - [x] Click into the query editor, then press `⌘K`. The doc explorer still opens and search is focused; Monaco should not swallow the shortcut. - [x] Confirm the old "Jump to schema" button in the top bar is gone. - [x] Open the shortcuts help dialog (`?` or the help entry) and confirm "Search in documentation" lists `⌘K` with no Alt/Option. Refs: #4219
## Summary - Remove the legacy vertical editor toolbar and the floating "GraphiQL" corner logo. Branding now lives only in the top bar, and editor actions live only in the tab strip. - Move Merge Fragments, previously available only in the old toolbar, into the tab-strip actions alongside Prettify, Copy, and Save, so the action isn't lost. - The composable `Toolbar`/`Logo` render-prop slots still work for embedders. Only the default rendering is removed. - Enlarge the tab-strip action buttons so they're easier to click. - Position the action buttons at the right edge of the query editor instead of the far right of the response pane, so they stay next to the query and follow the editor/response divider as it's resized. The sessions area is split into an editor column and a response column divided by the drag bar; the tab strip lives in the editor column. ## Test plan - [x] Open GraphiQL. There's no floating vertical button column and no corner logo over the editor; branding appears once, in the top bar. - [x] The tab strip shows Prettify, Merge Fragments, Copy, and Save, each exactly once. - [x] The action buttons sit at the right edge of the query editor (by the editor/response divider), not over the response pane, and follow the divider when it's dragged. - [x] Write a query that references a fragment, click Merge Fragments, and confirm the fragment gets inlined into the operation. - [x] Prettify reformats the query, Copy copies it, and the single Run button in the top bar still executes it. - [x] The Prettify, Merge, and Copy keyboard shortcuts still work. - [x] The tab-strip action buttons read as comfortably sized, not cramped, and stay evenly spaced and aligned with the tab labels. Refs: #4219
## Summary This PR does two related things for GraphQL identifier coloring across the first-party plugins. **1. Align colors across plugins.** Type names, field names, and argument names now use the same colors across the doc explorer, history, and query builder. Doc explorer was already internally consistent (orange types, green fields, purple arguments), so it's the reference the other two now follow. History's saved query labels (previously plain foreground text) are now the same green as field names. Query builder previously colored composite return types and type-condition names blue and left argument names uncolored; both now match doc explorer. **2. Color type names by category.** Instead of every type name being orange, type-name references are colored by GraphQL kind, so a field list is no longer a monotonous wall of orange: | Kind | Color | |---|---| | object / interface / union | orange | | scalar | blue | | enum | green | | input object | gold | This applies in the schema-aware plugins (doc explorer and query builder). The Monaco query editor is unchanged — it isn't schema-aware, so type names in query text stay orange. ### Public API The scheme is exposed so third-party plugins can conform to it and retheme it: - Four CSS tokens — `--type-composite`, `--type-scalar`, `--type-enum`, `--type-input` — are the supported retheming surface (documented in `tokens.css`). - `typeCategory(type)` is exported from `@graphiql/react`; it buckets any GraphQL type into `'scalar' | 'enum' | 'input' | 'composite'`, unwrapping list/non-null wrappers. Class names, internal selectors, and the `data-type-kind` attribute remain private. Light-theme `--type-input` uses a darker gold than the dark theme so it clears WCAG AA as text (~5–6.5:1 against the doc-explorer surfaces). ## Test plan - [x] **Alignment:** Open the doc explorer, history, and query builder in the same session. A field name is the same green in all three; an argument name is the same purple in both doc explorer and query builder. - [x] **Category colors (doc explorer):** Browse a schema with mixed field types. Scalar return types (`String`/`Int`/`Boolean`/`ID`/custom scalars) are blue, enum types green, object/interface/union types orange, and input object types gold. - [x] **Category colors (query builder):** Expand the field tree. Scalar-returning fields are blue, enum-returning fields green, object/interface/union fields orange. - [x] **Arguments (doc explorer):** A field with an input-object argument shows the input type name in gold, clearly distinct from the purple argument name beside it. - [x] **Type conditions (query builder):** Expand an interface/union field's possible types. The `... on TypeName` labels are orange. - [x] **Search (doc explorer):** Search results show type names colored by the same category scheme. - [x] **History:** Saved query entries show their label/operation name in green. - [x] **Light theme:** Switch themes and spot-check the same views. All four category colors stay legible against the panel background — the gold in particular should read as a clear dark gold, not washed out. Refs: #4219
## Summary Audited keyboard navigation across the current layout: top bar, activity rail, side panel, editor, response pane, variables/headers strip. Tab order is predictable end to end, and Shift+Tab retraces it exactly, so no reordering was needed there. Two real bugs turned up: - The Settings dialog trapped focus correctly and closed on Escape, but every close path (Escape, the X button, clicking outside) silently dropped focus to the page instead of sending it back to the gear button that opened the dialog. Radix restores focus to whatever had it when the dialog opened, but that snapshot kept getting overwritten by the dialog's own re-renders while it was open. `Dialog` now takes an explicit `restoreFocusRef` instead of relying on Radix's implicit tracking. - In the History panel, pressing Escape while renaming a saved query did nothing. The handler was checking for the key name `'Esc'`, which no browser has sent since Internet Explorer; it should be `'Escape'`. Fixed the check, and made both Escape and Save return focus to the row's edit button, since the rename input disappears on close and focus had nowhere else to go but the page. Added a Cypress spec that walks Tab and Shift+Tab through the whole app and exercises both fixes, along with the settings dialog's focus trap and the execute button's operation dropdown (already working fine, thanks to Radix). ## Test plan - [x] Load the app fresh, click anywhere non-interactive, then Tab. Focus should land on the top bar's method chip, then move through: Run button, activity rail icons, session tab strip, logo, operation editor, editor toolbar buttons, variables/headers tabs, response view picker, copy button, result window. Shift+Tab from the end should retrace the same path in reverse. - [x] Tab to the operation editor (it's a single stop) and press Enter. Focus should move into the actual Monaco text area so you can start typing. - [x] Open Settings with the gear button, then press Escape. The dialog should close and focus should land back on the gear (Tab afterward should move to the next rail item, not restart from the top of the page). - [x] Reopen Settings and close it by clicking the X, then again by clicking outside the dialog. Focus should return to the gear both times. - [x] While Settings is open, Tab repeatedly. Focus should stay inside the dialog the whole time and never escape to the page behind it. - [x] Run a query, open History, click the pencil icon on an entry to rename it, then press Escape. The rename input should disappear without saving, and focus should land on that row's edit button. - [ ] Write a query with two named operations, click the Run button's dropdown arrow, use arrow keys to move between them, then press Escape. The menu should close and focus should return to the Run button. - [ ] `yarn workspace graphiql cypress run --spec cypress/e2e/keyboard.cy.ts` passes. Refs: #4219
## What
Consolidate the pending changesets on `graphiql-6` so they describe the
finished v6 release rather than the path we took to build it.
`graphiql-6` is unreleased and in changesets pre-mode with `pre.json`
`changesets: []` (**no alpha has consumed any changeset yet**), so this
pending set becomes the `6.0.0` changelog verbatim. It had grown to **62
entries** written as if each incremental step would be felt by a user
("add `TopBar` with a placeholder URL" → later "wire the real URL"; a
dozen separate `restyle-*` bullets). None of those intermediate states
ship.
This collapses the set to **15 changesets** that read as the actual
changelog.
## Approach
| Bucket | Changesets |
| --- | --- |
| **Redesign** (one entry, self-explanatory) | `v6-redesign`: tokens,
light/dark theming, layout chrome (top bar, activity rail, side panel,
status bar, flattened editor, vars/headers strip), restyled components,
Monaco themes, doc explorer + history rebuilds, unified syntax colors,
browserslist |
| **One per feature** | `transport-api`, `response-pane`, `settings`,
`collections`, `query-builder`, `operation-name-follows-cursor` |
| **New `@graphiql/react` primitives** (new public API, one per concern)
| `keycap-hint`, `method-pill`, `segmented-control`, `panel-header` |
| **Breaking changes** (each stands alone) | `remove-deprecated-hooks`,
`drop-legacy-client-alias`, `explorer-deprecation` |
| **Unrelated package, untouched** | `graphql-value-string-highlighting`
(`vscode-graphql-syntax`) |
Feature changesets are written as end state. E.g. the response pane
header simply shows real status/time/size, with no mention of the
placeholders it briefly had.
## Editorial calls worth a look
- Dropped `v6-alpha-line` ("Initial v6 alpha"); its `#4219` reference
and migration-guide pointer fold into `v6-redesign`, which is now the
`graphiql` major headline.
- `history-method-pill` folds into the redesign's history bullet; its
small additive `QueryStoreItem.operation` field on `@graphiql/toolkit`
is no longer called out separately (toolkit is majored by
`drop-legacy-client-alias` regardless, and the redesign narrative
shouldn't land in the toolkit changelog).
- `query-builder` left at `plugin-query-builder: patch`, preserving
existing semver intent, not editing it. Flag if you want it minor.
## Validation
`changeset status` yields an **identical** set of package bumps before
and after, verified against `origin/graphiql-6`:
- **major:** `graphiql`, `@graphiql/react`, `@graphiql/toolkit`,
`@graphiql/plugin-doc-explorer`, `@graphiql/plugin-history`,
`@graphiql/plugin-explorer`
- **minor:** `@graphiql/plugin-collections`
- **patch:** `@graphiql/plugin-query-builder`, `vscode-graphql-syntax`,
`@graphiql/plugin-code-exporter` (transitive)
No version resolves differently; this is purely changelog-narrative
editing.
> Stacks on #4409 (keyboard navigation audit). Review that one first; this branch is based on it, so the diff here is only the labels-and-focus-ring work. ## Summary - Adds a single global focus ring. Any control focused with the keyboard now gets a 2px blue outline (`--accent-blue`) with a small offset, so nothing slips through without a visible focus indicator. The color clears WCAG's non-text contrast threshold against the canvas in both themes (about 9.7:1 in dark, 5.6:1 in light). Components that already drew their own ring keep it, and the global rule backstops the rest. - The doc explorer search box was the one focusable control with no visible focus state. The input cleared its own outline and nothing replaced it. It now shows the same ring while it has focus. - Decorative icons that sit next to a text label in the doc explorer (section headers, the FIELDS and arguments toggles, the two search magnifiers) are marked `aria-hidden`. A screen reader now reads just the label instead of also announcing "chevron down icon" or "magnifying glass icon" next to it. - The cancel button on a history label edit was icon-only with no name. It now has an accessible label, so it reads as "Cancel" instead of an unlabeled button. ## Test plan - [ ] Tab through the app. Every control you land on shows a clear blue focus ring, in both light and dark themes. - [ ] Open the doc explorer and Tab (or click) into the search box. The box shows a focus ring while it has focus. - [ ] With a screen reader on, move through the doc explorer. Section headers, the FIELDS toggle, and the argument toggles read their label once, without a trailing icon name. - [ ] In History, start editing a saved query's label and Tab to the cancel (X) button. It announces as "Cancel". - [ ] axe baseline holds: no new violation ids vs. the recorded baseline. Refs: #4219
…vider) (#4414) ## Summary Three small fixes to the response pane, grouped since they all touch the same surface: - **The loading spinner no longer shifts the layout.** It was rendered as a flex sibling above the response, so every query pushed the view picker and results down, then snapped back when the response arrived. It's now an absolutely-positioned overlay centered over the response body, so nothing reflows. It's also smaller and uses the accent color to match the header. - **Consistent inset across all three response views.** The JSON result window sat flush against the header, while Tree and Table used different insets (12/16px and 14px). All three now use the same `var(--px-16)` as the query and variables editors. - **Table view rows get their divider back.** The row border referenced `--border-subtle`, an undefined custom property, so per the CSS spec the whole declaration was dropped and rows rendered with no separator. It now uses `--border-muted`. ## Test plan - [x] Run a slow query. The spinner appears centered over the response area, and the JSON/Tree/Table view picker above it does not jump or reflow when the spinner appears or clears. - [x] With a result showing, confirm the JSON has an even 16px inset on all sides (matching the query editor), not flush to the header. Switch to Tree and Table views; the inset is the same in all three. - [x] Run a query returning a list and switch to Table view. Each row has a subtle bottom divider, consistent with the header separator. - [ ] `yarn workspace @graphiql/react test` and `yarn workspace graphiql test` pass. Refs: #4219
…iql/plugin-explorer` (#4416) ## Summary Collapses the two parallel color systems v6 shipped with — the legacy v5 `--color-*` HSL variables and the v6 OKLCH tokens — down to one, and removes the last consumer of the old set. - Migrates every first-party reader of `--color-*` to the v6 OKLCH tokens in `tokens.css` (core library, `plugin-code-exporter`, `plugin-collections`, and the `graphiql-webpack` example), following the idioms already used by the restyled components. - **Deprecates** `--color-*` rather than deleting it: the v5 README documented the whole `root.css` variable set as public retheming API, and the HSL→OKLCH format change rules out a transparent alias. The variables stay frozen at their v5 values with an inline deprecation comment; overriding them no longer re-themes GraphiQL, so consumers should move to the OKLCH tokens. - **Removes `@graphiql/plugin-explorer`** — the last first-party `--color-*` reader, now superseded by the default `@graphiql/plugin-query-builder`. It remains maintained on the v5 LTS branch. - Docs: the migration guide gains a `--color-*`→v6 mapping table and its `plugin-explorer` section moves from deprecation to removal; the READMEs point retheming at `tokens.css`. ## Test plan - [ ] On the deploy preview, toggle Light/Dark and confirm text, borders, icons, hover states, markdown deprecation admonitions, and the settings dialog all render correctly in both themes. - [ ] Confirm the default query-builder plugin still opens, and the explorer plugin is gone from the webpack and CDN examples. - [ ] In devtools, override `--color-primary` on `.graphiql-container` and confirm it no longer changes the UI (deprecated); override the matching OKLCH token (e.g. `--accent-blue`) and confirm it does. Refs: #4219
…4410) ## Summary Documents the v6 OKLCH design-token system in the `@graphiql/react` README's theming section — the `--bg-*` / `--border-*` / `--fg-*` / `--accent-*` / `--type-*` / `--btn-*` / `--radius-*` / `--shadow-*` families, the `L% C H` triplet convention, the type-name category tokens, and the exported `typeCategory` helper. Also marks the legacy v5 `--color-*` HSL variables deprecated and updates the migration-guide caveat now that the README documents both sets. Docs follow-up to #4416 (the `--color-*` deprecation and `@graphiql/plugin-explorer` removal). ## Test plan - [x] Render the README theming section: the OKLCH token list matches `tokens.css`, the `typeCategory` description is accurate, and the intra-page + `tokens.css` / migration-guide links resolve. Refs: #4219
## Summary The tab close button (`.graphiql-tab-close`) only changed text color on hover: no background, no rounded corners, no transition, and no `:focus-visible` outline. It's a keyboard-focusable button, so tabbing to it left no visible focus indicator at all. That's an accessibility gap, and it was the one icon button in the app that didn't match the hover/focus pattern used everywhere else: the activity rail, toolbar buttons, and the dialog close button. It now follows that same pattern. ## Test plan - [x] Open several editor tabs, keyboard-Tab to a tab's close (✕) button, and confirm it shows a clear blue focus ring. - [x] Hover the close button and confirm a subtle background appears behind it with rounded corners. - [x] Click the close button and confirm the tab still closes as before. - [x] Check both Dark and Light themes. The hover background and focus ring should be legible in each. Refs: #4219
## Summary The History panel had no empty state. With no run history and no favorites (first run, or right after "Clear"), it rendered only its header and nothing below, just blank space. Add a "No queries run yet." message for that case, styled to match the Collections panel's existing empty state. ## Test plan - [x] Open a fresh GraphiQL instance with no query history. Open the History panel and confirm it shows a "No queries run yet." message below the header instead of blank space. - [x] Run a query. Reopen the History panel and confirm the empty-state message is gone, replaced by the new history entry. - [x] Click "Clear" to remove all history and favorites. Confirm the empty-state message reappears. - [x] Favorite an item, then delete the remaining regular history so only the favorite is left. Confirm the empty-state message stays hidden (favorites alone should not trigger it). - [x] Check both Dark and Light themes. The empty-state text should be legible and match Collections' muted styling in each. Refs: #4219
## Summary - Brings back an explicit way to pick which operation runs. When the document has more than one operation, the Run button grows a caret. Clicking it opens a menu of every operation by name, and picking one sets it as the active operation and runs it, the same behavior the old execute button had. - Cursor tracking is still the default. Moving the cursor into a different operation re-activates it, even after a menu pick, so nothing gets pinned. The menu marks the currently active operation. - An operation that can't run under the current HTTP method (a mutation while GET is selected) shows disabled in the menu, the same block reason already shown on the button itself. - The Run control now has visible `:focus-visible` and `:active` states. Previously only the dead execute-button code had them. - Removed the unused `ExecuteButton` component now that the TopBar owns this menu directly. Keeping both around would leave two copies of the same picker logic. ## Test plan - [x] Write a document with two named operations. The Run button shows a caret sized to match the Run half (same height, one seam). No operation name sits beside the button. - [x] Click the caret. A menu lists both operations, with the active one marked. Pick the one the cursor is *not* in; that operation runs and becomes active. - [x] Move the cursor back into the other operation. It becomes active again (cursor still wins; the pick wasn't sticky) — confirm via the mark in the menu. - [x] Add an operation that's invalid for the current method (e.g. a mutation while on GET). Its menu item is disabled. - [x] With a single operation (or none), no caret appears. The button is a plain Run. - [x] Keyboard: focus the caret, open with Enter/Space, move between operations with arrow keys, press Escape. The menu closes and focus returns to the Run button. - [x] Looks modern: the menu matches the app's other dropdowns (elevated surface, rounded corners, subtle shadow, highlight-on-hover); the caret reads as one control with the green Run pill; both halves show a focus ring on keyboard focus and a press state. Verify in light and dark. Refs: #4288, #4219
## Summary
Reworks how the query builder handles fragments end to end.
**Extraction is now an inline row action.** Fragment extraction used to
be a single "Create fragment from selection" button pinned to the bottom
of the panel, acting on wherever the editor's text cursor happened to
sit. It now lives on the field rows: expand a composite field, tick the
children you want, and an "Extract to fragment" action appears on that
row. Triggering it lifts the selection into a named fragment and
replaces it with a spread (`user { ...UserFields }`). The generated name
(`UserFields`, de-duped when taken) stays editable in place from the
Fragments list.
**Extracted rows stay editable.** An extracted field no longer collapses
into a locked `...FragmentName` badge. It stays a normal, expandable
composite: the spread renders as a `...FragmentName` reference row among
the field's children, and ticking more fields adds them to the **base
query** alongside the spread. A field's only tie to its fragment is the
real spread in the document — it doesn't "remember" being extracted.
**Fragments are editable in place.** Because a fragment's contents are
shared across every field that spreads it, editing them needs its own
surface. The active editing target now **follows the editor cursor**:
move the cursor into a `fragment … { … }` block and the builder switches
to editing that fragment's tree, rooted at its type condition. You can
also jump in by clicking a `...FragmentName` reference row or a name in
the Fragments list; "Back to query" returns to the operation. Fragments
are flat peers — no nesting or breadcrumbs.
**The fragment reuse suggestion buttons are gone.** The short-lived "Use
...FragmentName" reuse action (and its dead helpers) is removed — it
crowded the row, and multiple matching fragments on one field overflowed
the small row.
The row actions are plain keyboard-focusable buttons. A dedicated
shortcut and keycap hint are left for the app-wide accessibility pass.
### Implementation notes
The document mutators and readers were generalized from an
operation-only `operationName` to a `DefinitionTarget` (`operation |
fragment`), so the same field tree and handlers drive operation and
fragment editing alike. Emptying a fragment is a no-op (a fragment can't
be emptied without orphaning its spreads); promote-arg-to-variable stays
operation-only.
## Test plan
- [x] Open the query builder against a schema. Expand a composite field
(e.g. `person`), tick a scalar child (e.g. `name`), and confirm an
"Extract to fragment" action appears on that row.
- [x] Trigger it. Confirm the query reads `person { ...PersonFields }`
with a `fragment PersonFields on Person { name }` block, and the
`person` row now shows a `...PersonFields` reference among its children.
- [x] With the field still expanded, tick another child (e.g. `age`).
Confirm it lands in the **base query** — `person { ...PersonFields age
}` — and the fragment is unchanged.
- [x] Click the `...PersonFields` reference row. Confirm a focused
fragment editor opens (header `PersonFields on Person`, "Back to query"
control), rooted at `Person`.
- [x] Tick a field in the fragment editor. Confirm it edits the fragment
(`fragment PersonFields on Person { name age }`) and the operation still
just spreads it.
- [x] Move the editor cursor into the `fragment PersonFields …` block.
Confirm the builder switches to editing that fragment. Move it back into
the operation; confirm it switches back.
- [x] Click a fragment's name in the Fragments list. Confirm it focuses
that fragment and is highlighted as active.
- [x] In the Fragments list, rename `PersonFields`. Confirm the
definition and every `...PersonFields` reference update together.
- [x] Keyboard-only: Tab to the extract action / reference row /
fragment name, confirm each takes focus and activates with Enter/Space.
- [x] Unit and `query-builder.cy.ts` suites pass; Storybook a11y check
clean.
Refs: #4219
## Summary The doc explorer's search input and results popover had their shadow hardcoded to `0 4px 12px oklch(0% 0 0 / 0.3)`. That's the dark-theme value of `--shadow-popover`, pasted in as a literal instead of a reference, and it's missing the token's second, subtler shadow layer too. Every other popover in the app (dialog, dropdown, tooltip) reads `var(--shadow-popover)` and re-themes on its own, so in light theme this one was casting a shadow roughly four times heavier than its siblings. Both spots now read the token directly. ## Test plan - [x] Switch to light theme, open the doc explorer, and focus the search box. The active-state input and the results popover both cast a soft, light-appropriate shadow, not a heavy dark one. - [x] Switch to dark theme and confirm the search popover shadow still matches other popovers (dialog, dropdown, tooltip). Refs: #4219
This was referenced Jul 13, 2026
…zing) (#4421) ## Summary - `MethodPill`'s query/mutation/subscription colors were hand-typed OKLCH values that approximated `--accent-green`/`--accent-yellow`/`--accent-purple` but didn't reference them, so the pills never re-themed with the rest of the app. They now read the tokens directly. Query and subscription flip their text color between near-black and near-white depending on theme, the same split `.graphiql-button-success` and the query-builder's variable toggle already use, since the token values (unlike the old hardcoded ones) differ enough per theme to change which text color clears contrast. - The status bar's item gap (`--px-14`) didn't match the top bar's (`--px-12`), its closest structural sibling, for no obvious reason. Both now use `--px-12`. Leaving the status bar's mono/small/muted type scale alone on purpose here, that reads as a deliberate "quiet metadata strip" choice, not a bug. - Icon buttons in the toolbar, activity rail, and response header each sized themselves differently: a legacy `--toolbar-width` variable, a hardcoded 32px, and a hardcoded 28px override. Added a shared `--icon-button-size-sm/md/lg` scale to the density presets in `tokens.css` and pointed all three at it. ## Test plan - [x] Switch between light and dark themes and confirm the QRY/MUT/SUB method pills recolor with the rest of the accent palette and their text stays legible in both. - [x] Confirm the status bar's item spacing now matches the top bar's. - [x] At each density preset (Compact/Comfortable/Spacious in Settings), confirm icon buttons in the activity rail, top-bar toolbar, and response header size consistently with each other. - [x] In the response header, confirm the toolbar icon buttons still fit comfortably inside the 32px-tall header bar without touching its edges. Refs: #4219
…ills (#4422) ## Summary - The doc-explorer's `field-card`, `type-card`, and `fields-list` all hardcoded `font-size` in px (11.5px, 14px, 10px, 9px, and more), so none of that text grew or shrank with the font-size preset. They now read `--font-size-mono`, `--font-size-small`, or `--font-size-eyebrow` depending on which is closest to the original size. - `response-header`, `panel-header`, and `method-pill` had the same problem: fixed px values that ignored Settings > Font size entirely. Migrated to the matching tokens. - The legacy `--font-size-hint` token (`root.css`) was a frozen value that never scaled with the preset, at ~19 call sites across `var-headers-strip`, the collections plugin, the history plugin, and the doc-explorer plugin. Every usage now points at `--font-size-eyebrow` instead, and the dead `--font-size-hint` definition is gone. - Along the way, found that `--font-size-body` itself is silently shadowed by a frozen legacy value in `root.css` (same class of bug, but out of scope here, so flagged separately). Avoided that token in the new mappings and used `--font-size-mono` for the mono-family names/types instead, since it actually rescales. ## Test plan - [x] Open Settings, set Font Size to Large or Extra Large, then open the Documentation Explorer. Confirm type names, field names/types, descriptions, and the "FIELDS · N" section label all grow with it (they previously stayed fixed). - [x] Run a query and confirm the response header (status code, timing, size) grows with the font-size setting. - [x] Confirm panel headers (e.g. the "History" and "Documentation Explorer" panel titles) grow with the setting. - [x] Confirm method pills (QRY/MUT/SUB badges) grow with the setting. - [x] Set the font size back to Default and confirm everything returns to its original size with nothing shifted or clipped. - [x] Check both Dark and Light themes at a couple of font-size presets to confirm nothing overlaps or truncates awkwardly at the larger sizes. Refs: #4228
## Summary `@graphiql/react` had a `cn` helper that was just a re-export of `clsx`, left over from before the Button/IconButton restyle. It didn't add any behavior of its own, so this removes it and has every internal usage import `clsx` directly instead: all the component files in `graphiql-react` that used `cn(...)`, plus `@graphiql/plugin-history` and the `graphiql` package, which both imported `cn` from `@graphiql/react` rather than depending on `clsx` themselves. Both packages now list `clsx` as a direct dependency. Since `cn` was exported from `@graphiql/react`'s public entry point, dropping it is a breaking change for 6.0.0. Added a changeset with a major bump and a short note in the v6 migration guide pointing anyone using `cn` at `clsx`. Refs: #4228
…4424) ## Summary List rows across the side panels had four different "this row is highlighted" recipes doing the same job. Activity rail, dropdown menu items, and Collections rows all used a flat `oklch(var(--bg-subtle))` background on hover, which is the pattern the rest of the app's interactive chrome had already settled on. Doc explorer rows instead tinted with `--fg-default` at a few different alpha values, History rows tinted with `--accent-blue`, and History's own action icons (rename/favorite/delete) used yet a fourth recipe (`--fg-default` at a different alpha again), all in the same file as the third. Doc explorer (fields list, schema overview, and both search listboxes) and History now hover with the same flat `--bg-subtle` background as Collections, and History's action icons match too. `--accent-blue` is reserved for genuine active/selected state: the active field row in doc explorer, and the editable/rename row in History, both of which are untouched. Separately, only three files in the whole app had a hover `transition`, each with its own duration (120ms, 100ms, 120ms/150ms), while nearly everything else snapped instantly. Rather than pick a side, this PR adds a single `--transition-fast: 120ms ease` token to `tokens.css` and applies it to every row hover touched here, so the newly-unified hover reads as one consistent, intentional motion instead of an arbitrary mix. The three pre-existing transition durations elsewhere are unchanged for now, out of scope for this pass. ## Test plan - [x] Hover rows in the Doc Explorer (schema types, fields, search results), History, and Collections panels; confirm all three show the same subtle flat highlight, no blue or gray tint. - [x] Select/activate a row (e.g. the currently-viewed field in the Doc Explorer, or a row being renamed in History) and confirm it uses the accent-blue treatment, clearly distinct from plain hover. - [x] Confirm hover feels consistent across panels, including the small action icons (rename/favorite/delete) on History rows. - [x] Check both Dark and Light themes. Refs: #4228
#4425) ## Summary - Remove the composable `GraphiQL.Toolbar` and `GraphiQL.Logo` slots. #4398 already stopped rendering their v5 defaults, leaving them in an awkward half-fit spot with nowhere natural left to render; this finishes the job by removing the slots themselves. - `GraphiQL.Toolbar` is a straight removal, no deprecation period first. Custom editor actions belong on a plugin's `sessionActions` now, rendered into the tab strip alongside Prettify/Merge/Copy/Save. - `GraphiQL.Logo` is removed now that `<TopBar>` takes a new `brand` prop (any `ReactNode`), which is also exposed straight off `<GraphiQL>`. It replaces the default hexagon icon + "GraphiQL" wordmark; leave it unset to keep the default. - `GraphiQL.Footer` is untouched. - Also drops the dead `toolbar.additionalContent`/`toolbar.additionalComponent` `TypeError` guards, since they pointed at the component being removed here, and the `.graphiql-toolbar`/`.graphiql-logo` CSS those slots used. - Updates the README and the v6 migration guide with before/after snippets, and migrates the `graphiql-vite-react-router` example off both slots. Builds on #4398. ## Test plan - [x] Render `<GraphiQL>` with no extra props. The top bar shows the default hexagon icon and "GraphiQL" wordmark. - [x] Render `<GraphiQL brand="My Company">`. The top bar shows "My Company" instead, and the default wordmark is gone. - [x] Confirm the old `GraphiQL.Toolbar`/`GraphiQL.Logo` slots are gone: referencing `<GraphiQL.Toolbar>` or `<GraphiQL.Logo>` as JSX children is now a TypeScript error, and there's no runtime path that renders them. - [x] Confirm toolbar actions still work from the tab strip: Prettify, Merge Fragments, Copy, and Save each appear once and behave as before. - [x] Register a plugin with a `sessionActions` component and confirm it renders in the tab strip alongside the built-in actions. - [x] Confirm `<GraphiQL.Footer>` still renders below the response, unchanged. - [x] Run the `graphiql-vite-react-router` example and confirm the branding shows "API Explorer" and the share-query button still works from the tab strip. Refs: #4228 (builds on #4398)
5 tasks
## Summary - `--font-size-body` is declared twice: as a scaling preset token in `tokens.css` (`:root, [data-font-size='default'|'compact'|'large'|'xl']`), and as a frozen v5 value in `root.css`'s DEPRECATED `.graphiql-container` block. Both selectors have specificity (0,1,0) and the frozen block comes later (after `@import 'tokens.css'`), so `calc(15rem / 16)` always won. - Every consumer of `var(--font-size-body)` — the `.graphiql-container` base font-size, `button`, `dialog`, `dropdown-menu`, `top-bar`, and the collections / doc-explorer / query-builder plugins — has silently been pinned to 15px, so Settings > Font size never touched body text even though the code looked correct. The sibling preset tokens (`--font-size-small`, `--font-size-mono`, `--font-size-eyebrow`) were unaffected because they aren't redeclared in that block. - Renaming the frozen declaration separates the two meanings and lets the preset token win. ## Changes - `root.css`: renamed `--font-size-body: calc(15rem / 16)` to `--font-size-body-legacy` in the DEPRECATED `.graphiql-container` block. - `graphiql-plugin-code-exporter/src/index.css`: pointed the CodeMirror-based plugin at `var(--font-size-body-legacy)` so it keeps rendering at the frozen 15px. - Updated the DEPRECATED block comment to name the code-exporter plugin — the explorer plugin it referenced no longer consumes this token. ## Validation Steps 1. Open the app and note the size of body text (panel headers, buttons, top bar, doc explorer rows). 2. Open Settings > Font size and switch to **Large**, then **Extra large**. Body text throughout the app should visibly grow at each step — previously it stayed fixed at 15px regardless. 3. Switch to **Compact**. Body text should visibly shrink. 4. Switch back to **Default** and confirm body text settles at the standard size (slightly smaller than before this change). 5. Open the code exporter plugin and cycle through the font-size presets. Its exported-code panel should stay the same size at every preset — it intentionally remains frozen.
…4428) ## Summary `tokens.css` declared `--font-family: 'Inter'` and `--font-family-mono: 'JetBrains Mono'`, but nothing in the app ever rendered in either. `root.css` redeclared both variables on `.graphiql-container`, pointing them back at Roboto and Fira Code, and that selector beats `:root` in the cascade. `packages/graphiql/src/style.css` only ever `@import`s the Roboto and Fira Code `@font-face` files, so Inter and JetBrains Mono weren't loaded to begin with — even if the cascade had gone the other way, the browser would have fallen through to the generic fallbacks. We decided in #4426 to keep Roboto and Fira Code rather than swap the type system, so this points the canonical declaration in `tokens.css` at the fonts that actually ship and drops the now-redundant `.graphiql-container` override. One declaration, and it matches what you see. No font loading changes. ## Test plan - [x] Open the app and inspect the query editor and any body text. Computed `font-family` resolves to Fira Code and Roboto respectively, same as before this change. - [x] In DevTools, confirm `--font-family` on `.graphiql-container` now inherits from `:root` rather than being overridden locally, and that its value is Roboto. - [x] Switch themes and density presets and confirm no type regressions anywhere in the app (top bar, status bar, doc explorer, history, response pane). Refs: #4219
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tracking PR for the GraphiQL v6 redesign effort. This is the long-running integration branch that hosts work-in-progress against
main.Individual PRs target
graphiql-6and produce alpha releases via changesets pre-mode. When v6 is ready to ship, this branch will be merged intomain.See discussion #4219 for background and progress updates.
Closes #734 — the visual query builder ships in v6 as
@graphiql/plugin-query-builder.