Skip to content

GraphiQL v6 - #4228

Draft
trevor-scheer wants to merge 123 commits into
mainfrom
graphiql-6
Draft

GraphiQL v6#4228
trevor-scheer wants to merge 123 commits into
mainfrom
graphiql-6

Conversation

@trevor-scheer

@trevor-scheer trevor-scheer commented May 7, 2026

Copy link
Copy Markdown
Contributor

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-6 and produce alpha releases via changesets pre-mode. When v6 is ready to ship, this branch will be merged into main.

See discussion #4219 for background and progress updates.

Closes #734 — the visual query builder ships in v6 as @graphiql/plugin-query-builder.

## 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-bot

changeset-bot Bot commented May 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5dc814d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
@graphiql/react Major
@graphiql/plugin-history Major
graphiql Major
@graphiql/plugin-doc-explorer Major
@graphiql/plugin-collections Major
@graphiql/toolkit Major
@graphiql/plugin-query-builder Major
@graphiql/plugin-code-exporter Major
cm6-graphql Major
codemirror-graphql Major
graphql-language-service Major
graphql-language-service-cli Major
graphql-language-service-server Major
monaco-graphql Major
vscode-graphql Major
vscode-graphql-execution Major
graphiql-e2e Patch

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

@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

The latest changes of this PR are not available as canary, since there are no linked changesets for this PR.

## 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
…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)
## 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
## Summary

An audit of `@graphiql/react`'s public barrels turned up a handful of
exports that read as internal or dangerously generic. None have docs or
a changeset, which is itself the signal they weren't meant to be public.
Since everything here is unreleased on `graphiql-6`, this is the last
window to fix it without a deprecation cycle.

- `getRunBlockReason`, `resolveActiveOperation`, and
`MUTATION_REQUIRES_POST_REASON` were re-exported from `utility/index.ts`
even though they're pure Run-button implementation detail. Dropped from
the barrel; the two internal call sites (`stores/execution.ts`,
`top-bar`) now import `./run-block` directly.
- `VarTab` (exported alongside `VarHeadersStrip`) is about as generic as
a type name gets in a shared package namespace. Renamed to
`VarHeadersTab`.
- `TopBarView` / `StatusBarView` / `SidePanelView` (and their prop
types) are the presentational half of a container/presentational split,
exported alongside `TopBar` / `StatusBar` / `SidePanel`. Nothing outside
`graphiql-react` imports them — even their own tests and stories already
import from the component's own file, not the barrel — so they read as
Storybook plumbing, not consumer API. Dropped from the barrel. The
container components stay public, and so do the genuine response views
(`ResponseTreeView`, `ResponseTableView`), which are real documented
JSON/Tree/Table components, not a naming coincidence with the `*View`
pattern above.
- `PortalProvider` / `usePortalContainer` stay public. They're not
leaked plumbing: `graphiql`'s own `<GraphiQL>` component uses them to
wire its container element into Radix's dialog/tooltip/dropdown portals
so themed content doesn't fall back to rendering into `document.body`.
Anyone assembling a custom shell around `@graphiql/react` components
directly needs the same hook. The contract is already documented in the
module's JSDoc.

Refs: #4219
…f the public barrel (#4454)

Builds on #4431. `ResponseHeader`, `ResponseTreeView`, and
`ResponseTableView` are rendered only by `ResponseEditor` and have no
consumers anywhere else in the repo, so this moves them under
`components/response-editor/` next to the editor that uses them and
drops them from the public barrel. Their behavior is tuned to a GraphQL
response body: the tree flattens the `data` envelope, and the table
guesses which list-of-objects to render. Exporting them would freeze
those heuristics into the 6.0 contract, and adding an export back later
is cheap while removing one after 6.0 is not.

No changeset: none of the three exist on `main`, so they are added and
removed within the same unreleased cycle. `ResponseEditor` itself is
unchanged and still exported.
`MessageProcessor.spec.ts` asserts the definition ranges of a type in
the generated schema by absolute line number. The `Test` type grew by
three lines when the `json` custom-scalar field from #4448 merged in
from `main` and landed alongside the query builder fields already on
this branch. This updates the expected line numbers.

## Test plan

- [x] `yarn test` passes for `graphql-language-service-server` (9 files,
116 passing, 1 skipped)
- [x] Vitest Unit Tests goes green on this PR, and on a v6 PR rebased on
top of it
`ExecuteButton` was dropped from `@graphiql/react` when the run control
moved into the top bar, leaving no exported run button for anyone
assembling a custom toolbar. This pulls the control back out into its
own component, exports it under the v5 name and signature (`FC`, no
props), and has the top bar render it, so there's one run button rather
than two implementations. It also restores v5's stop behavior, which the
top bar's copy had dropped: the button becomes Stop while fetching or
subscribed, and stays enabled there even when a fresh run would be
blocked.

CSS classes move from `graphiql-top-bar-run*` to
`graphiql-execute-button*` now that the control isn't top bar specific.

## Test plan

- [ ] Run button still renders as one green pill in the top bar, with
the caret half when the document has several operations
- [ ] Picking an operation from the caret menu runs that operation
- [ ] A mutation over GET still disables Run and explains why on hover
- [ ] Starting a request flips the button to Stop, and clicking it
cancels
- [ ] `ExecuteButton` imports from `@graphiql/react` and renders outside
the top bar
… accept header (#4429)

A handful of independent correctness fixes to the v6 transport path,
mostly around error handling and cancellation.

**Non-JSON response bodies.** The transports called `response.json()`
unconditionally, so a body that isn't JSON (an HTML error page from a
proxy, a plain-text 401) made `send()` reject and lose exactly the wire
metadata the transport exists to provide. `readResponseBody` now reads
the body as text and folds a parse failure into a synthetic `{ errors:
[{ message: <raw text> }] }` body, keeping the real status, headers,
timing, and size. A plain-text `Bad Gateway` now renders as `Bad
Gateway`, which also let the Cypress test stop pinning Electron's
JSON-parser wording.

**`ok` semantics.** `TransportResponse.ok` only consulted the GraphQL
body, so a 401 whose body parsed as JSON with no `errors` was `ok:
true`. It's now `response.ok && !hasErrors` for HTTP responses.
Subscription events have no HTTP envelope, so they stay body-only.

**Subscription teardown.** Stopping a subscription didn't actually stop
it. The store's unsubscriber called
`iter[Symbol.asyncIterator]().return?.()`, but
`TransportHookRegistry.wrap()` mints a brand-new iterator on every
`[Symbol.asyncIterator]()` call, so Stop disposed a throwaway while the
one driving the `for await` loop kept delivering events forever. The
store now captures the iterator once and disposes that _same_ iterator.
The legacy fetcher's iterable path had the identical pattern and gets
the identical fix. New tests in `execution.spec.ts` assert delivery
halts after `stop()`, both against a raw transport and through the real
`wrap()`.

**Aborting queries/mutations.** They had no cancellation at all.
`TransportRequest` gains `signal`, threaded down to `fetch`, and
`stop()` now aborts the in-flight request. `stop()` also bumps `queryId`
so a transport that ignores the signal still can't paint a late
response, and `run()`'s `catch` distinguishes a user-initiated abort
(silent) from a genuine failure (surfaced). For streams the controller
stays armed as an HTTP-level backstop; primary teardown is still
iterator disposal.

**`onError` plugin hook.** Plugins could observe requests and responses
but not failures. `transport.onError` fires when `send()` rejects
outright (a network error, a throwing `onBeforeSend` hook), receiving
the request as transformed by whichever hooks had run. Observer
callbacks are now isolated: one that throws is logged instead of
breaking the stream or masking the original error. `wrap()` also handles
disposal races: `return()` during an awaited `onBeforeSend` prevents the
underlying `send()` from firing, and late values/errors after disposal
are dropped.

**Spec `accept` header.** With incremental delivery on (the default),
the multipart transport sent `accept: application/json, multipart/mixed`
with no mention of `application/graphql-response+json`, silently asking
spec-compliant servers for legacy response semantics. It now sends
`multipart/mixed, application/graphql-response+json;q=0.9,
application/json;q=0.8`; without explicit q-values every type ties at
q=1 and the server's tie-break can route an incremental response away
from multipart.

**Smaller items.** `TransportRequest` and `SubscriptionRequest` gain
`extensions` (in the URL for `GET`, in the body for `POST`/`QUERY`) for
things like automatic persisted queries; the two near-identical parse
passes in `createTransport` collapse into one
`classifySelectedOperation`; the never-populated `ResolverTrace` type is
removed from `TransportResponse.timing`.
## Summary

A docs review cross-checked the migration guide and package READMEs
against the actual `graphiql-6` source and found several that describe
APIs that no longer exist. The migration guide's
`useDocExplorer`/`useHistory` "after" examples pass a selector argument
and destructure fields (`.navStack`, `.items`) that don't exist — both
hooks take zero arguments and return the value directly, so copying the
examples as written is a compile error. The `graphiql` and
`graphiql-react` READMEs describe `transport` as "a function," which is
the old `Fetcher` shape; it's an object with a `send()` method. The
`createTransport` link in `graphiql/README.md` pointed at
`createFetcher.ts`, and neither README mentioned that `fetcher` still
works. The "Editor Theme" section documented a CodeMirror API that
hasn't existed since the Monaco rewrite. The toolkit's own README never
mentioned `createTransport`, its biggest new export.
`create-fetcher.md`'s deprecation banner linked to a
`create-transport.md` file that doesn't exist, and (found while fixing
that line) the migration-guide link right next to it was also pointing
one directory too shallow.

Also added the `ExecuteButton` removal to the migration guide's
breaking-changes notes, reworded the `.browserslistrc` line (the file
still exists, only its contents changed to `defaults`), and added the v6
migration guide to `graphiql/README.md`'s getting-started list, which
already linked to it later in the doc but didn't list it up top.

Refs: #4219
Update changesets for correctness
We're honestly probably ready for `rc` but going to get at least one
`beta` release published.
changelog-github attributes each entry to the commit that added the
changeset file, so the four files #4411 created (v6-redesign,
collections, response-pane, settings) all credited the consolidation
PR instead of the work. Its pr: directive overrides that; also aim
explorer-removal.md at #4416 (the PR that removed the plugin) instead
of the PR that added the changeset
trevor-scheer and others added 9 commits August 29, 2026 08:31
The 1.0.0-beta.0 seed occupied the first beta number, so the first
published prerelease would have been 1.0.0-beta.1. From a 0.0.0 base a
major changeset targets 1.0.0, and betas start at 1.0.0-beta.0
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to graphiql-6, this
PR will be updated.

⚠️⚠️⚠️⚠️⚠️⚠️

`graphiql-6` is currently in **pre mode** so this branch has prereleases
rather than normal releases. If you want to exit prereleases, run
`changeset pre exit` on `graphiql-6`.

⚠️⚠️⚠️⚠️⚠️⚠️

# Releases
## graphiql@6.0.0-beta.0

### Major Changes

- [#4416](#4416)
[`9c153e2`](9c153e2)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! -
`@graphiql/plugin-explorer` is removed. Its visual query-building UI is
replaced by `@graphiql/plugin-query-builder`, which is default-installed
in the `graphiql` meta-package, so the capability is available with no
extra setup. If you installed and registered `@graphiql/plugin-explorer`
yourself, drop the dependency and the `plugins` entry; if you relied on
the default plugin set, there is nothing to change.

- [#4425](#4425)
[`ff2e4ca`](ff2e4ca)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Remove the
composable `GraphiQL.Toolbar` and `GraphiQL.Logo` slots. Editor actions
are now contributed through a plugin's `sessionActions`, and branding is
customized through the `brand` prop passed to `<GraphiQL>` (or
`<TopBar>` directly). `GraphiQL.Footer` is unchanged. See the migration
guide for before/after examples.

- [#4228](#4228) Thanks
[@trevor-scheer](https://github.com/trevor-scheer)! - A ground-up visual
redesign for v6. A new OKLCH-based design-token system brings
first-class light and dark themes, driven by a `data-theme` attribute on
the GraphiQL container. The layout is rebuilt around a top bar (endpoint
and Run action), a left activity rail for plugins, a resizable side
panel, a slim status bar, a flattened editor workspace, and a
Variables/Headers tab strip. Every built-in component and both Monaco
editor themes are restyled to match, and the doc explorer and history
panels are rebuilt on the new chrome.

GraphQL syntax coloring is unified across the doc explorer, history, and
query builder, with type names colored by category. The mapping is
public API for retheming: the `--type-scalar`, `--type-enum`,
`--type-input`, and `--type-composite` CSS tokens, plus the
`typeCategory` helper exported from `@graphiql/react`.

Custom CSS that overrides GraphiQL's internal class names may need
updating; only the CSS custom properties (design tokens) are supported
theming API. The build now targets the `defaults` browserslist preset,
which covers the modern browsers the OKLCH color system requires. See
the migration guide at `docs/migration/graphiql-6.0.0.md`. Refs
graphql/graphiql#4219.

### Minor Changes

- [#4359](#4359)
[`ac56840`](ac56840)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - New
`@graphiql/plugin-collections` plugin for saving named operations into
folder collections and reusing them later, default-installed in the
`graphiql` meta-package so a Collections rail icon appears out of the
box (passing the `plugins` prop opts out of the default set as before).
- Collapsible tree UI with inline rename, hover-revealed row actions,
and QRY/MUT/SUB pills (a `MIX` pill when a saved document holds more
than one operation).
- Save the current operation with ⌘S/Ctrl+S or the tab-strip Save
button. An operation opened from a collection stays linked, so re-saving
updates it in place; otherwise a "Save to collection" dialog opens.
Clicking a saved item opens it in a new tab.
- Reorder within and across collections by drag-and-drop or keyboard
(focus a row's drag handle, Space to grab, arrow keys to move, Space to
drop, Escape to cancel).
- Copy a raw query or Share an importable envelope from any row;
collection headers expose Share. Paste or drop a collections export
anywhere in the pane to merge it in, or import/export JSON from the
dialog. Imports reconcile by stable id, so re-importing updates in place
and never duplicates; a conflict dialog lets you apply incoming changes,
keep yours, or review each, and merge never deletes.
- Pluggable persistence via the `storage` option (defaults to
`localStorage`), plus `readOnly`, `allowImportExport`, and
`allowReplace` for governed deployments.

To support this without the core depending on any specific plugin,
`@graphiql/react` gains a save API: `registerSaveHandler(handler)` (⌘S
and the Save button fan out to every registered handler plus the
`onSaveQuery` prop, and the dirty-state affordance only appears when at
least one is registered), the `onSaveQuery(tab)` prop with
`markTabSaved(tabId)` for deferred saves, and
`GraphiQLPlugin.sessionActions`, an always-mounted plugin slot for
toolbar buttons, dialogs, or behavior registration. The dirty-state dot
means "a saved operation has unsaved edits" and survives a reload, so a
tab that was never saved reads clean.

- [#4352](#4352)
[`f8a9445`](f8a9445)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - The active
operation now follows the editor cursor. As you move the cursor between
operations in a multi-operation document, `operationName` updates to the
operation the cursor sits in, so the operation dropdown and
operation-aware plugins all reflect where you are editing. Previously
`operationName` only changed on run-at-cursor or via the operation
dropdown.

Two consequences if you embed GraphiQL: the `onEditOperationName`
callback now fires when the cursor crosses into a different named
operation, and a tab containing multiple operations shows the active
operation name with a `+N` count of the others. Pinning an operation
with the `operationName` prop still overrides cursor tracking.

The Run button now offers an operation picker: in a document with
multiple named operations, a dropdown on the Run button lets you choose
which operation to run, and the menu marks which operation is currently
active. The active operation still follows the editor cursor by default.

- [#4352](#4352)
[`f8a9445`](f8a9445)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add
`@graphiql/plugin-query-builder`, a first-party visual query builder. It
renders the schema's root types as a collapsible tree; checking a field
adds it to the current operation and unchecking removes it, with the
document parsed, mutated, and reprinted through the `graphql` package's
AST utilities. Fields expose argument inputs (scalars, enums, lists, and
input objects, including lists of input objects), scalar arguments can
be promoted to variables, named fragments can be extracted from a
field's selection and edited in place, and union/interface fields offer
inline-fragment type-condition selectors.

The query builder is default-installed in the `graphiql` meta-package,
so it is available with no extra setup. It takes over from
`@graphiql/plugin-explorer`.

- [#4321](#4321)
[`03535ab`](03535ab)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
response pane header with real status, elapsed time, and response size
from the active transport, a copy button, and a JSON / Tree / Table view
toggle (the selection is persisted and restored on reload).
- **Tree** renders the response JSON as a collapsible tree with
type-colored values; top-level nodes expand by default and deeper levels
start collapsed.
- **Table** renders each list field as its own table captioned with its
path (e.g. `test.person.friends`); sibling and aliased lists each get a
table, nested objects and arrays show as shorthand summaries, non-list
responses show an empty state, and rows get a bottom divider.

- [#4338](#4338)
[`480afc1`](480afc1)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
`SettingsDialog` with theme, density, font-size, and persist-headers
controls, backed by a new `useGraphiQLSettings()` hook that persists
preferences to `localStorage` and applies them to the GraphiQL container
via `data-*` attributes. Density and font-size presets fill in concrete
token values for the `[data-density]` and `[data-font-size]` blocks in
`tokens.css`; Monaco editor font size, the status bar, and UI icon sizes
follow the active font-size preset. The `forcedTheme` and
`showPersistHeadersSettings` props continue to work, with `forcedTheme`
hiding the theme control.

- [#4333](#4333)
[`093cb10`](093cb10)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
structured `Transport` API alongside the existing `Fetcher`.
`createTransport({...})` performs the GraphQL request and returns a
`TransportResponse` carrying the real HTTP wire metadata (status,
headers, timing, size) for queries, mutations, subscriptions, and
incremental delivery, so the response pane can surface those values
directly instead of fabricating them. That metadata is there even when
the response body isn't valid JSON (an HTML error page from a proxy, a
plain-text 401), so a broken response still shows its real status code
instead of a generic error. `<GraphiQL>` accepts a new `transport` prop,
mutually exclusive with `fetcher` at the type level.

Transports support GET, POST, and the [HTTP
`QUERY`](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/)
method per the GraphQL over HTTP spec. Pass `method` /
`supportedMethods` to choose; GET encodes the query into the URL with no
body, `QUERY` sends a JSON body but is safe and idempotent, and
mutations are always sent over POST (or blocked when POST is
unavailable). `Transport` exposes `url`, `method`, `supportedMethods`,
and an optional `setMethod`, and the top bar shows the active method and
endpoint with an inline switcher that cycles through the supported
methods. Every request, incremental delivery on or off, sends
`application/graphql-response+json` in its `accept` header alongside
`application/json`, so spec-compliant servers don't fall back to legacy
response semantics. Subscriptions require an explicit
`subscriptionClient` satisfying a small `SubscriptionClient` contract: a
single `.subscribe(request, sink)` method that `graphql-ws` and
`graphql-sse` clients meet directly. The low-level `simpleHttpTransport`
and `multipartHttpTransport` primitives also accept an optional
`method`.

`TransportRequest` carries `extensions` for GraphQL-over-HTTP extensions
such as automatic persisted queries (encoded into the URL for `GET`,
included in the JSON body for `POST` and `QUERY`), and `signal`, an
`AbortSignal` that cancels an in-flight query or mutation. Stopping a
running query or mutation aborts the request; stopping a subscription
closes the underlying socket or SSE connection. `TransportResponse.ok`
reflects both layers: the HTTP status and the absence of top-level
GraphQL errors, so a 401 or 500 is never `ok: true` just because its
body happens to parse as JSON with no `errors`.

Plugins can observe and transform traffic through
`transport.onBeforeSend`, `transport.onResponse`, and
`transport.onError`, available via `useGraphiQLPluginContext()` (all
three return a cleanup function; the `transport` field is `undefined`
under the legacy `fetcher` path, so guard with optional chaining).
`onError` fires when a request fails outright, such as a network error,
so plugins can react to failures the same way they observe successful
responses.

`createGraphiQLFetcher`, the `Fetcher` type and its companions, and
`<GraphiQL fetcher={...}>` are deprecated but continue to work
unchanged. Consumers on the deprecated path see a one-time dismissible
banner in the response pane pointing at
`docs/migration/graphiql-6.0.0.md` rather than fabricated
status/timing/size values. The CDN bundle exposes
`GraphiQL.createTransport` and `GraphiQL.createWsClient` so script-tag
consumers can adopt without a bundler.

### Patch Changes

- [#4409](#4409)
[`0f96193`](0f96193)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - History
label edits can now be canceled with Escape, and focus returns to the
row's edit button instead of dropping to the page. `Dialog` gains an
optional `restoreFocusRef` prop for returning focus to a specific
element on close.

- Updated dependencies
[[`0f96193`](0f96193),
[`1919f6a`](1919f6a),
[`b6f8dc6`](b6f8dc6),
[`d4f0268`](d4f0268),
[`c25bfd5`](c25bfd5),
[`f8a9445`](f8a9445),
[`1ce71e4`](1ce71e4),
[`f8a9445`](f8a9445),
[`f45e26b`](f45e26b),
[`827da62`](827da62),
[`b6f8dc6`](b6f8dc6),
[`a0fe11a`](a0fe11a),
[`b6f8dc6`](b6f8dc6),
[`093cb10`](093cb10),
[`b6f8dc6`](b6f8dc6)]:
  - @graphiql/react@1.0.0-beta.0
  - @graphiql/plugin-history@1.0.0-beta.0
  - @graphiql/plugin-doc-explorer@1.0.0-beta.0
  - @graphiql/plugin-collections@1.0.0-beta.0
  - @graphiql/plugin-query-builder@1.0.0-beta.0
## @graphiql/plugin-collections@1.0.0-beta.0

### Major Changes

- [#4359](#4359)
[`ac56840`](ac56840)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - New
`@graphiql/plugin-collections` plugin for saving named operations into
folder collections and reusing them later, default-installed in the
`graphiql` meta-package so a Collections rail icon appears out of the
box (passing the `plugins` prop opts out of the default set as before).
- Collapsible tree UI with inline rename, hover-revealed row actions,
and QRY/MUT/SUB pills (a `MIX` pill when a saved document holds more
than one operation).
- Save the current operation with ⌘S/Ctrl+S or the tab-strip Save
button. An operation opened from a collection stays linked, so re-saving
updates it in place; otherwise a "Save to collection" dialog opens.
Clicking a saved item opens it in a new tab.
- Reorder within and across collections by drag-and-drop or keyboard
(focus a row's drag handle, Space to grab, arrow keys to move, Space to
drop, Escape to cancel).
- Copy a raw query or Share an importable envelope from any row;
collection headers expose Share. Paste or drop a collections export
anywhere in the pane to merge it in, or import/export JSON from the
dialog. Imports reconcile by stable id, so re-importing updates in place
and never duplicates; a conflict dialog lets you apply incoming changes,
keep yours, or review each, and merge never deletes.
- Pluggable persistence via the `storage` option (defaults to
`localStorage`), plus `readOnly`, `allowImportExport`, and
`allowReplace` for governed deployments.

To support this without the core depending on any specific plugin,
`@graphiql/react` gains a save API: `registerSaveHandler(handler)` (⌘S
and the Save button fan out to every registered handler plus the
`onSaveQuery` prop, and the dirty-state affordance only appears when at
least one is registered), the `onSaveQuery(tab)` prop with
`markTabSaved(tabId)` for deferred saves, and
`GraphiQLPlugin.sessionActions`, an always-mounted plugin slot for
toolbar buttons, dialogs, or behavior registration. The dirty-state dot
means "a saved operation has unsaved edits" and survives a reload, so a
tab that was never saved reads clean.

### Patch Changes

- Updated dependencies
[[`0f96193`](0f96193),
[`1919f6a`](1919f6a),
[`b6f8dc6`](b6f8dc6),
[`d4f0268`](d4f0268),
[`c25bfd5`](c25bfd5),
[`f8a9445`](f8a9445),
[`1ce71e4`](1ce71e4),
[`f45e26b`](f45e26b),
[`827da62`](827da62),
[`b6f8dc6`](b6f8dc6),
[`a0fe11a`](a0fe11a),
[`b6f8dc6`](b6f8dc6),
[`093cb10`](093cb10),
[`b6f8dc6`](b6f8dc6)]:
  - @graphiql/react@1.0.0-beta.0
## @graphiql/plugin-doc-explorer@1.0.0-beta.0

### Major Changes

- [#4393](#4393)
[`827da62`](827da62)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Remove
deprecated hooks: `useEditorContext`, `useExecutionContext`,
`usePluginContext`, `useSchemaContext`, `useTheme`, `useStorage`,
`useStorageContext`, `usePrettifyEditors`, `useCopyQuery`,
`useMergeQuery`, the `*Store` aliases (in `@graphiql/react`);
`useExplorerContext` (in `@graphiql/plugin-doc-explorer`); and
`useHistoryContext` (in `@graphiql/plugin-history`). Replacements were
available since v5 — see the v6 migration guide for one-line
replacements.

### Minor Changes

- [#4228](#4228) Thanks
[@trevor-scheer](https://github.com/trevor-scheer)! - A ground-up visual
redesign for v6. A new OKLCH-based design-token system brings
first-class light and dark themes, driven by a `data-theme` attribute on
the GraphiQL container. The layout is rebuilt around a top bar (endpoint
and Run action), a left activity rail for plugins, a resizable side
panel, a slim status bar, a flattened editor workspace, and a
Variables/Headers tab strip. Every built-in component and both Monaco
editor themes are restyled to match, and the doc explorer and history
panels are rebuilt on the new chrome.

GraphQL syntax coloring is unified across the doc explorer, history, and
query builder, with type names colored by category. The mapping is
public API for retheming: the `--type-scalar`, `--type-enum`,
`--type-input`, and `--type-composite` CSS tokens, plus the
`typeCategory` helper exported from `@graphiql/react`.

Custom CSS that overrides GraphiQL's internal class names may need
updating; only the CSS custom properties (design tokens) are supported
theming API. The build now targets the `defaults` browserslist preset,
which covers the modern browsers the OKLCH color system requires. See
the migration guide at `docs/migration/graphiql-6.0.0.md`. Refs
graphql/graphiql#4219.

### Patch Changes

- [#4413](#4413)
[`1919f6a`](1919f6a)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
global keyboard focus ring and fill in a few missing screen-reader
labels. Every control now shows a clearly visible blue outline when
focused with the keyboard, with enough contrast against the canvas in
both light and dark themes. Decorative icons that sit next to a text
label no longer announce a redundant name, the doc explorer search box
shows a focus ring while typing, and the cancel button on a history
label edit now has an accessible name.

- Updated dependencies
[[`0f96193`](0f96193),
[`1919f6a`](1919f6a),
[`b6f8dc6`](b6f8dc6),
[`d4f0268`](d4f0268),
[`c25bfd5`](c25bfd5),
[`f8a9445`](f8a9445),
[`1ce71e4`](1ce71e4),
[`f45e26b`](f45e26b),
[`827da62`](827da62),
[`b6f8dc6`](b6f8dc6),
[`a0fe11a`](a0fe11a),
[`b6f8dc6`](b6f8dc6),
[`093cb10`](093cb10),
[`b6f8dc6`](b6f8dc6)]:
  - @graphiql/react@1.0.0-beta.0
## @graphiql/plugin-history@1.0.0-beta.0

### Major Changes

- [#4393](#4393)
[`827da62`](827da62)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Remove
deprecated hooks: `useEditorContext`, `useExecutionContext`,
`usePluginContext`, `useSchemaContext`, `useTheme`, `useStorage`,
`useStorageContext`, `usePrettifyEditors`, `useCopyQuery`,
`useMergeQuery`, the `*Store` aliases (in `@graphiql/react`);
`useExplorerContext` (in `@graphiql/plugin-doc-explorer`); and
`useHistoryContext` (in `@graphiql/plugin-history`). Replacements were
available since v5 — see the v6 migration guide for one-line
replacements.

### Patch Changes

- [#4409](#4409)
[`0f96193`](0f96193)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - History
label edits can now be canceled with Escape, and focus returns to the
row's edit button instead of dropping to the page. `Dialog` gains an
optional `restoreFocusRef` prop for returning focus to a specific
element on close.

- [#4413](#4413)
[`1919f6a`](1919f6a)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
global keyboard focus ring and fill in a few missing screen-reader
labels. Every control now shows a clearly visible blue outline when
focused with the keyboard, with enough contrast against the canvas in
both light and dark themes. Decorative icons that sit next to a text
label no longer announce a redundant name, the doc explorer search box
shows a focus ring while typing, and the cancel button on a history
label edit now has an accessible name.

- [#4228](#4228) Thanks
[@trevor-scheer](https://github.com/trevor-scheer)! - A ground-up visual
redesign for v6. A new OKLCH-based design-token system brings
first-class light and dark themes, driven by a `data-theme` attribute on
the GraphiQL container. The layout is rebuilt around a top bar (endpoint
and Run action), a left activity rail for plugins, a resizable side
panel, a slim status bar, a flattened editor workspace, and a
Variables/Headers tab strip. Every built-in component and both Monaco
editor themes are restyled to match, and the doc explorer and history
panels are rebuilt on the new chrome.

GraphQL syntax coloring is unified across the doc explorer, history, and
query builder, with type names colored by category. The mapping is
public API for retheming: the `--type-scalar`, `--type-enum`,
`--type-input`, and `--type-composite` CSS tokens, plus the
`typeCategory` helper exported from `@graphiql/react`.

Custom CSS that overrides GraphiQL's internal class names may need
updating; only the CSS custom properties (design tokens) are supported
theming API. The build now targets the `defaults` browserslist preset,
which covers the modern browsers the OKLCH color system requires. See
the migration guide at `docs/migration/graphiql-6.0.0.md`. Refs
graphql/graphiql#4219.

- Updated dependencies
[[`0f96193`](0f96193),
[`1919f6a`](1919f6a),
[`b6f8dc6`](b6f8dc6),
[`26ae143`](26ae143),
[`d4f0268`](d4f0268),
[`c25bfd5`](c25bfd5),
[`f8a9445`](f8a9445),
[`1ce71e4`](1ce71e4),
[`f45e26b`](f45e26b),
[`827da62`](827da62),
[`b6f8dc6`](b6f8dc6),
[`a0fe11a`](a0fe11a),
[`b6f8dc6`](b6f8dc6),
[`093cb10`](093cb10),
[`b6f8dc6`](b6f8dc6)]:
  - @graphiql/react@1.0.0-beta.0
  - @graphiql/toolkit@1.0.0-beta.0
## @graphiql/plugin-query-builder@1.0.0-beta.0

### Major Changes

- [#4352](#4352)
[`f8a9445`](f8a9445)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add
`@graphiql/plugin-query-builder`, a first-party visual query builder. It
renders the schema's root types as a collapsible tree; checking a field
adds it to the current operation and unchecking removes it, with the
document parsed, mutated, and reprinted through the `graphql` package's
AST utilities. Fields expose argument inputs (scalars, enums, lists, and
input objects, including lists of input objects), scalar arguments can
be promoted to variables, named fragments can be extracted from a
field's selection and edited in place, and union/interface fields offer
inline-fragment type-condition selectors.

The query builder is default-installed in the `graphiql` meta-package,
so it is available with no extra setup. It takes over from
`@graphiql/plugin-explorer`.

### Patch Changes

- Updated dependencies
[[`0f96193`](0f96193),
[`1919f6a`](1919f6a),
[`b6f8dc6`](b6f8dc6),
[`d4f0268`](d4f0268),
[`c25bfd5`](c25bfd5),
[`f8a9445`](f8a9445),
[`1ce71e4`](1ce71e4),
[`f45e26b`](f45e26b),
[`827da62`](827da62),
[`b6f8dc6`](b6f8dc6),
[`a0fe11a`](a0fe11a),
[`b6f8dc6`](b6f8dc6),
[`093cb10`](093cb10),
[`b6f8dc6`](b6f8dc6)]:
  - @graphiql/react@1.0.0-beta.0
## @graphiql/react@1.0.0-beta.0

### Major Changes

- [#4423](#4423)
[`f45e26b`](f45e26b)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - `cn` is no
longer exported from `@graphiql/react`; import `clsx` directly.

- [#4393](#4393)
[`827da62`](827da62)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Remove
deprecated hooks: `useEditorContext`, `useExecutionContext`,
`usePluginContext`, `useSchemaContext`, `useTheme`, `useStorage`,
`useStorageContext`, `usePrettifyEditors`, `useCopyQuery`,
`useMergeQuery`, the `*Store` aliases (in `@graphiql/react`);
`useExplorerContext` (in `@graphiql/plugin-doc-explorer`); and
`useHistoryContext` (in `@graphiql/plugin-history`). Replacements were
available since v5 — see the v6 migration guide for one-line
replacements.

### Minor Changes

- [#4359](#4359)
[`ac56840`](ac56840)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - New
`@graphiql/plugin-collections` plugin for saving named operations into
folder collections and reusing them later, default-installed in the
`graphiql` meta-package so a Collections rail icon appears out of the
box (passing the `plugins` prop opts out of the default set as before).
- Collapsible tree UI with inline rename, hover-revealed row actions,
and QRY/MUT/SUB pills (a `MIX` pill when a saved document holds more
than one operation).
- Save the current operation with ⌘S/Ctrl+S or the tab-strip Save
button. An operation opened from a collection stays linked, so re-saving
updates it in place; otherwise a "Save to collection" dialog opens.
Clicking a saved item opens it in a new tab.
- Reorder within and across collections by drag-and-drop or keyboard
(focus a row's drag handle, Space to grab, arrow keys to move, Space to
drop, Escape to cancel).
- Copy a raw query or Share an importable envelope from any row;
collection headers expose Share. Paste or drop a collections export
anywhere in the pane to merge it in, or import/export JSON from the
dialog. Imports reconcile by stable id, so re-importing updates in place
and never duplicates; a conflict dialog lets you apply incoming changes,
keep yours, or review each, and merge never deletes.
- Pluggable persistence via the `storage` option (defaults to
`localStorage`), plus `readOnly`, `allowImportExport`, and
`allowReplace` for governed deployments.

To support this without the core depending on any specific plugin,
`@graphiql/react` gains a save API: `registerSaveHandler(handler)` (⌘S
and the Save button fan out to every registered handler plus the
`onSaveQuery` prop, and the dirty-state affordance only appears when at
least one is registered), the `onSaveQuery(tab)` prop with
`markTabSaved(tabId)` for deferred saves, and
`GraphiQLPlugin.sessionActions`, an always-mounted plugin slot for
toolbar buttons, dialogs, or behavior registration. The dirty-state dot
means "a saved operation has unsaved edits" and survives a reload, so a
tab that was never saved reads clean.

- [#4277](#4277)
[`d4f0268`](d4f0268)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
`KeycapHint` primitive for displaying inline keyboard shortcuts (e.g.
`⌘K`, `⌘⏎`), available for general consumer use. It takes semantic
modifier names via the `MODIFIER` constant: `MODIFIER.Meta` renders as
`⌘` on macOS and `Ctrl` elsewhere; `Ctrl`/`Alt`/`Shift` render as Mac
glyphs (`⌃`/`⌥`/`⇧`) on macOS and plain text on other platforms; `Enter`
renders as `⏎` everywhere.

- [#4285](#4285)
[`c25bfd5`](c25bfd5)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
`MethodPill` primitive: a small colored pill labeling an operation as
QRY (query), MUT (mutation), or SUB (subscription).

- [#4352](#4352)
[`f8a9445`](f8a9445)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - The active
operation now follows the editor cursor. As you move the cursor between
operations in a multi-operation document, `operationName` updates to the
operation the cursor sits in, so the operation dropdown and
operation-aware plugins all reflect where you are editing. Previously
`operationName` only changed on run-at-cursor or via the operation
dropdown.

Two consequences if you embed GraphiQL: the `onEditOperationName`
callback now fires when the cursor crosses into a different named
operation, and a tab containing multiple operations shows the active
operation name with a `+N` count of the others. Pinning an operation
with the `operationName` prop still overrides cursor tracking.

The Run button now offers an operation picker: in a document with
multiple named operations, a dropdown on the Run button lets you choose
which operation to run, and the menu marks which operation is currently
active. The active operation still follows the editor cursor by default.

- [#4284](#4284)
[`1ce71e4`](1ce71e4)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
`PanelHeader` primitive for side panels. Renders a title, optional
subtitle, and optional action-icon row.

- [#4321](#4321)
[`03535ab`](03535ab)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
response pane header with real status, elapsed time, and response size
from the active transport, a copy button, and a JSON / Tree / Table view
toggle (the selection is persisted and restored on reload).
- **Tree** renders the response JSON as a collapsible tree with
type-colored values; top-level nodes expand by default and deeper levels
start collapsed.
- **Table** renders each list field as its own table captioned with its
path (e.g. `test.person.friends`); sibling and aliased lists each get a
table, nested objects and arrays show as shorthand summaries, non-list
responses show an empty state, and rows get a bottom divider.

- [#4282](#4282)
[`a0fe11a`](a0fe11a)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
`SegmentedControl` primitive for selecting one option from a small set
inline, used by the response view toggle and several settings controls.
It is built on native radio inputs, so keyboard navigation (arrow keys,
Home / End) and screen-reader semantics come from the browser and the
group is a single tab stop.

- [#4338](#4338)
[`480afc1`](480afc1)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
`SettingsDialog` with theme, density, font-size, and persist-headers
controls, backed by a new `useGraphiQLSettings()` hook that persists
preferences to `localStorage` and applies them to the GraphiQL container
via `data-*` attributes. Density and font-size presets fill in concrete
token values for the `[data-density]` and `[data-font-size]` blocks in
`tokens.css`; Monaco editor font size, the status bar, and UI icon sizes
follow the active font-size preset. The `forcedTheme` and
`showPersistHeadersSettings` props continue to work, with `forcedTheme`
hiding the theme control.

- [#4333](#4333)
[`093cb10`](093cb10)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
structured `Transport` API alongside the existing `Fetcher`.
`createTransport({...})` performs the GraphQL request and returns a
`TransportResponse` carrying the real HTTP wire metadata (status,
headers, timing, size) for queries, mutations, subscriptions, and
incremental delivery, so the response pane can surface those values
directly instead of fabricating them. That metadata is there even when
the response body isn't valid JSON (an HTML error page from a proxy, a
plain-text 401), so a broken response still shows its real status code
instead of a generic error. `<GraphiQL>` accepts a new `transport` prop,
mutually exclusive with `fetcher` at the type level.

Transports support GET, POST, and the [HTTP
`QUERY`](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/)
method per the GraphQL over HTTP spec. Pass `method` /
`supportedMethods` to choose; GET encodes the query into the URL with no
body, `QUERY` sends a JSON body but is safe and idempotent, and
mutations are always sent over POST (or blocked when POST is
unavailable). `Transport` exposes `url`, `method`, `supportedMethods`,
and an optional `setMethod`, and the top bar shows the active method and
endpoint with an inline switcher that cycles through the supported
methods. Every request, incremental delivery on or off, sends
`application/graphql-response+json` in its `accept` header alongside
`application/json`, so spec-compliant servers don't fall back to legacy
response semantics. Subscriptions require an explicit
`subscriptionClient` satisfying a small `SubscriptionClient` contract: a
single `.subscribe(request, sink)` method that `graphql-ws` and
`graphql-sse` clients meet directly. The low-level `simpleHttpTransport`
and `multipartHttpTransport` primitives also accept an optional
`method`.

`TransportRequest` carries `extensions` for GraphQL-over-HTTP extensions
such as automatic persisted queries (encoded into the URL for `GET`,
included in the JSON body for `POST` and `QUERY`), and `signal`, an
`AbortSignal` that cancels an in-flight query or mutation. Stopping a
running query or mutation aborts the request; stopping a subscription
closes the underlying socket or SSE connection. `TransportResponse.ok`
reflects both layers: the HTTP status and the absence of top-level
GraphQL errors, so a 401 or 500 is never `ok: true` just because its
body happens to parse as JSON with no `errors`.

Plugins can observe and transform traffic through
`transport.onBeforeSend`, `transport.onResponse`, and
`transport.onError`, available via `useGraphiQLPluginContext()` (all
three return a cleanup function; the `transport` field is `undefined`
under the legacy `fetcher` path, so guard with optional chaining).
`onError` fires when a request fails outright, such as a network error,
so plugins can react to failures the same way they observe successful
responses.

`createGraphiQLFetcher`, the `Fetcher` type and its companions, and
`<GraphiQL fetcher={...}>` are deprecated but continue to work
unchanged. Consumers on the deprecated path see a one-time dismissible
banner in the response pane pointing at
`docs/migration/graphiql-6.0.0.md` rather than fabricated
status/timing/size values. The CDN bundle exposes
`GraphiQL.createTransport` and `GraphiQL.createWsClient` so script-tag
consumers can adopt without a bundler.

- [#4228](#4228) Thanks
[@trevor-scheer](https://github.com/trevor-scheer)! - A ground-up visual
redesign for v6. A new OKLCH-based design-token system brings
first-class light and dark themes, driven by a `data-theme` attribute on
the GraphiQL container. The layout is rebuilt around a top bar (endpoint
and Run action), a left activity rail for plugins, a resizable side
panel, a slim status bar, a flattened editor workspace, and a
Variables/Headers tab strip. Every built-in component and both Monaco
editor themes are restyled to match, and the doc explorer and history
panels are rebuilt on the new chrome.

GraphQL syntax coloring is unified across the doc explorer, history, and
query builder, with type names colored by category. The mapping is
public API for retheming: the `--type-scalar`, `--type-enum`,
`--type-input`, and `--type-composite` CSS tokens, plus the
`typeCategory` helper exported from `@graphiql/react`.

Custom CSS that overrides GraphiQL's internal class names may need
updating; only the CSS custom properties (design tokens) are supported
theming API. The build now targets the `defaults` browserslist preset,
which covers the modern browsers the OKLCH color system requires. See
the migration guide at `docs/migration/graphiql-6.0.0.md`. Refs
graphql/graphiql#4219.

### Patch Changes

- [#4409](#4409)
[`0f96193`](0f96193)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - History
label edits can now be canceled with Escape, and focus returns to the
row's edit button instead of dropping to the page. `Dialog` gains an
optional `restoreFocusRef` prop for returning focus to a specific
element on close.

- [#4413](#4413)
[`1919f6a`](1919f6a)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
global keyboard focus ring and fill in a few missing screen-reader
labels. Every control now shows a clearly visible blue outline when
focused with the keyboard, with enough contrast against the canvas in
both light and dark themes. Decorative icons that sit next to a text
label no longer announce a redundant name, the doc explorer search box
shows a focus ring while typing, and the cancel button on a history
label edit now has an accessible name.

- Updated dependencies
[[`26ae143`](26ae143),
[`093cb10`](093cb10)]:
  - @graphiql/toolkit@1.0.0-beta.0
## @graphiql/toolkit@1.0.0-beta.0

### Major Changes

- [#4392](#4392)
[`26ae143`](26ae143)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Remove the
deprecated `legacyClient` alias from `CreateFetcherOptions`. It
duplicated `legacyWsClient` — pass `legacyWsClient` instead.

### Minor Changes

- [#4333](#4333)
[`093cb10`](093cb10)
Thanks [@trevor-scheer](https://github.com/trevor-scheer)! - Add a
structured `Transport` API alongside the existing `Fetcher`.
`createTransport({...})` performs the GraphQL request and returns a
`TransportResponse` carrying the real HTTP wire metadata (status,
headers, timing, size) for queries, mutations, subscriptions, and
incremental delivery, so the response pane can surface those values
directly instead of fabricating them. That metadata is there even when
the response body isn't valid JSON (an HTML error page from a proxy, a
plain-text 401), so a broken response still shows its real status code
instead of a generic error. `<GraphiQL>` accepts a new `transport` prop,
mutually exclusive with `fetcher` at the type level.

Transports support GET, POST, and the [HTTP
`QUERY`](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/)
method per the GraphQL over HTTP spec. Pass `method` /
`supportedMethods` to choose; GET encodes the query into the URL with no
body, `QUERY` sends a JSON body but is safe and idempotent, and
mutations are always sent over POST (or blocked when POST is
unavailable). `Transport` exposes `url`, `method`, `supportedMethods`,
and an optional `setMethod`, and the top bar shows the active method and
endpoint with an inline switcher that cycles through the supported
methods. Every request, incremental delivery on or off, sends
`application/graphql-response+json` in its `accept` header alongside
`application/json`, so spec-compliant servers don't fall back to legacy
response semantics. Subscriptions require an explicit
`subscriptionClient` satisfying a small `SubscriptionClient` contract: a
single `.subscribe(request, sink)` method that `graphql-ws` and
`graphql-sse` clients meet directly. The low-level `simpleHttpTransport`
and `multipartHttpTransport` primitives also accept an optional
`method`.

`TransportRequest` carries `extensions` for GraphQL-over-HTTP extensions
such as automatic persisted queries (encoded into the URL for `GET`,
included in the JSON body for `POST` and `QUERY`), and `signal`, an
`AbortSignal` that cancels an in-flight query or mutation. Stopping a
running query or mutation aborts the request; stopping a subscription
closes the underlying socket or SSE connection. `TransportResponse.ok`
reflects both layers: the HTTP status and the absence of top-level
GraphQL errors, so a 401 or 500 is never `ok: true` just because its
body happens to parse as JSON with no `errors`.

Plugins can observe and transform traffic through
`transport.onBeforeSend`, `transport.onResponse`, and
`transport.onError`, available via `useGraphiQLPluginContext()` (all
three return a cleanup function; the `transport` field is `undefined`
under the legacy `fetcher` path, so guard with optional chaining).
`onError` fires when a request fails outright, such as a network error,
so plugins can react to failures the same way they observe successful
responses.

`createGraphiQLFetcher`, the `Fetcher` type and its companions, and
`<GraphiQL fetcher={...}>` are deprecated but continue to work
unchanged. Consumers on the deprecated path see a one-time dismissible
banner in the response pane pointing at
`docs/migration/graphiql-6.0.0.md` rather than fabricated
status/timing/size values. The CDN bundle exposes
`GraphiQL.createTransport` and `GraphiQL.createWsClient` so script-tag
consumers can adopt without a bundler.
## @graphiql/plugin-code-exporter@5.1.4-beta.0

### Patch Changes

- Updated dependencies
[[`0f96193`](0f96193),
[`1919f6a`](1919f6a),
[`b6f8dc6`](b6f8dc6),
[`d4f0268`](d4f0268),
[`c25bfd5`](c25bfd5),
[`f8a9445`](f8a9445),
[`1ce71e4`](1ce71e4),
[`f45e26b`](f45e26b),
[`827da62`](827da62),
[`b6f8dc6`](b6f8dc6),
[`a0fe11a`](a0fe11a),
[`b6f8dc6`](b6f8dc6),
[`093cb10`](093cb10),
[`b6f8dc6`](b6f8dc6)]:
  - @graphiql/react@1.0.0-beta.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…exact beta versions (#4471)

Due to an old `@graphiql/react@1.0.0-next.*` publish, fresh installs of
the v6 beta release are resolving incorrect versions of
`@graphiql/react`. This pins the peer for now, but we should move back
to a range once we're in `rc` or moving to stable. This is just a
temporary workaround.
The PR Cypress job has flaked three times since July:

- [July
11](https://github.com/graphql/graphiql/actions/runs/29142257631)
- [July
12](https://github.com/graphql/graphiql/actions/runs/29205056576)
- [August
29](https://github.com/graphql/graphiql/actions/runs/33260100561)

Each run relaunches Electron, then times out in `cy.visit`; the failure
screenshot shows the app had rendered. The failing logs also include
Dawn/Vulkan GPU-process crashes, while a [passing
run](https://github.com/graphql/graphiql/actions/runs/33259452404) from
the same day does not. This resembles [Cypress issue
#23801](cypress-io/cypress#23801).

This passes `--disable-gpu` to Cypress Electron through
[`ELECTRON_EXTRA_LAUNCH_ARGS`](https://docs.cypress.io/app/references/launching-browsers#Electron-Browser).
I cannot prove the GPU crash causes the flake, so if it recurs with this
flag, we should try Cypress retries or Chrome.
The v6 packages are already receiving major releases, so this is the
right time to raise their GraphQL.js peer ranges to `^16.11.0 ||
^17.0.0`. This drops releases that predate the OneOf input fixes.

Package development uses the latest v16, `^16.14.2`, because some
upstream tools do not yet support GraphQL 17. The isolated
incremental-delivery test server uses the latest GraphQL 17, `^17.0.2`.
The v6 light-theme accessibility baselines currently accept failures in
query-tab semantics, text contrast, and an unnamed documentation link.
The final audit also found that the blocked-method animation drops its
11px label to 2.47:1 contrast, Monaco comments render at 2.48:1 against
the light editor background, and Monaco's dimmed final line number can
render at 1.74:1.

This keeps the method label at full opacity while pulsing a halo,
represents query tabs as a native list of independent selection and
close buttons, and uses the muted theme token for Monaco comments and
dimmed line numbers. The app axe baseline is empty after those fixes.
The capture step now writes once after the complete Cypress run, so
updating one checkpoint can't discard findings collected from another.

The commits are intended to be read in order. Commits 1–2 reproduce and
fix the method-label contrast issue, commits 3–4 expose and fix the
query-tab structure, commits 7–8 reproduce and fix the Monaco comment
contrast issue, and commits 11–12 cover the dimmed line number. The
intervening commits make the app audit deterministic, remove the final
baseline, and report exact failing targets.

No changeset. The pending v6 redesign changeset already covers this
work.

Refs: #4219
The `graphiql` package has shipped ESM-only since v5, but its repository
build still produced an unpublished UMD adapter and the main Cypress
suite loaded it. This removes that second integration seam.

The production test page now loads an unpublished E2E application built
from `src/e2e.ts`. That application resolves `graphiql` through this
checkout's built `dist/index.js` and bundles the local workspace
packages and selected GraphQL version, so the suite exercises the same
package entry point as consumers without falling back to published
betas. Vite development still loads source, and `examples/graphiql-cdn`
remains the supported import-map CDN example.

The README and v6 migration guide now describe the ESM package contract.
No changeset: the published UMD build was removed in GraphiQL 5, and the
pending v6 changeset already covers the current release work.

## Test plan

- [ ] Serve the production build and confirm the browser requests
`/dist/e2e/index.js`.
- [ ] Run a query and subscription, then try prettify, fragment merging,
history, and theme changes in the production page.
- [ ] Start the Vite development server and confirm the page loads from
`src/e2e.ts`.
- [ ] Open `examples/graphiql-cdn` and confirm the import-map example
still works.

Refs: #4219
The GraphiQL demo and Cypress harness live inside the published
`graphiql` package. That makes the package build responsible for test
output, lets `CI` choose the runtime architecture implicitly, and
spreads fixture setup across Vite, Cypress, the test server, and
Netlify.

This moves that infrastructure into a private `graphiql-e2e` workspace.
The `source` target always runs Vite against the workspace source, while
the `built` target bundles against `packages/graphiql/dist/index.js`.
The test server owns HTTP and GraphQL subscriptions on one
browser-visible origin. Netlify stages the same built app without trying
to open a WebSocket to the visitor's `localhost`.

Published `graphiql` consumers should see no runtime or package API
change. Demo and E2E dependencies move out of the package workspace, and
E2E output stays out of the tarball.

This PR is stacked on #4486.

## Review by commit

The commits are intended to be read in order. The first two move the
harness and make the `source` and `built` targets explicit. Commits
three and four reproduce and fix the subscription-origin bug. Commits
five and six protect the package boundary and introduce the Cypress
fixture interface. The final four make clean-checkout builds
self-contained and preserve the fixture's readiness contract.

## Manual verification

1. Open the [Netlify
preview](https://deploy-preview-4488--graphiql-test.netlify.app), run `{
__typename }`, and inspect the browser's Network panel. The app assets
should load from `/e2e/assets/`, the GraphQL request should use
`/.netlify/functions/graphql`, and the page shouldn't attempt a
WebSocket connection to `localhost`.
2. Run `yarn dev:graphiql` and open `http://localhost:5173`. Run `{
__typename }`, then `subscription { message(delay: 0) }`. Both
operations should complete, with `/graphql` and `/subscriptions` using
the page's `localhost:5173` origin.
3. Run `yarn build:graphiql`, then `yarn workspace graphiql-e2e
server:built`, and open `http://localhost:8080`. Repeat the query and
subscription. The browser should load the entry script from
`/e2e/assets/`, and both operations should use the page's
`localhost:8080` origin.
4. Run `yarn workspace graphiql pack --dry-run --json` and inspect the
file list. It should contain the library output under `dist`, with no
`cypress/`, `test/`, or `dist/e2e/` paths.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Visual query builder from Graphql schema

1 participant