GraphiQL v6 - #4228
Draft
trevor-scheer wants to merge 123 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: 5dc814d The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 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
…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
## 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`.
…solves the workspace copy
## 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
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.
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.