From 71f60cf6777d4cb553130c00c4134eae605a6134 Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Sun, 12 Jul 2026 17:32:33 -0700 Subject: [PATCH 1/4] Add a `brand` prop to TopBar for custom branding TopBar currently hardcodes the GraphQL hexagon icon and "GraphiQL" wordmark. Add an optional `brand` prop (any `ReactNode`) that renders in its place, defaulting to the existing icon + wordmark when unset. This is the replacement branding slot for the `GraphiQL.Logo` removal that follows. --- .../src/components/top-bar/index.tsx | 23 +++++++++++++++---- .../components/top-bar/top-bar.stories.tsx | 17 ++++++++++++++ .../src/components/top-bar/top-bar.test.tsx | 8 +++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/graphiql-react/src/components/top-bar/index.tsx b/packages/graphiql-react/src/components/top-bar/index.tsx index 9d577191e7f..18a937c5976 100644 --- a/packages/graphiql-react/src/components/top-bar/index.tsx +++ b/packages/graphiql-react/src/components/top-bar/index.tsx @@ -2,7 +2,7 @@ // opt this file out so `useGraphiQL` / `useGraphiQLActions` stay live. 'use no memo'; -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import type { HttpMethod } from '@graphiql/toolkit'; import type { OperationDefinitionNode } from 'graphql'; import { useGraphiQL, useGraphiQLActions } from '../provider'; @@ -16,9 +16,14 @@ import './index.css'; export type TopBarProps = { /** Version string shown in the brand pill. */ version?: string; + /** + * Custom branding rendered in place of the default GraphiQL icon + wordmark. + * @default the GraphiQL hexagon icon and "GraphiQL" wordmark + */ + brand?: ReactNode; }; -export const TopBar: FC = ({ version }) => { +export const TopBar: FC = ({ version, brand }) => { const { run, setTransportMethod, setOperationName } = useGraphiQLActions(); const isFetching = useGraphiQL(state => state.isFetching); const transport = useGraphiQL(state => state.transport); @@ -42,6 +47,7 @@ export const TopBar: FC = ({ version }) => { return ( = ({ version }) => { export type TopBarViewProps = { version?: string; + brand?: ReactNode; isFetching: boolean; url: string; method: HttpMethod; @@ -79,6 +86,7 @@ export type TopBarViewProps = { export const TopBarView: FC = ({ version, + brand, isFetching, url, method, @@ -202,8 +210,15 @@ export const TopBarView: FC = ({ return (
-
diff --git a/packages/graphiql-react/src/components/top-bar/top-bar.stories.tsx b/packages/graphiql-react/src/components/top-bar/top-bar.stories.tsx index 09eb8ebecda..1e2185c8923 100644 --- a/packages/graphiql-react/src/components/top-bar/top-bar.stories.tsx +++ b/packages/graphiql-react/src/components/top-bar/top-bar.stories.tsx @@ -100,6 +100,23 @@ export const NoVersion: Story = { ], }; +/** Custom branding in place of the default hexagon icon + "GraphiQL" wordmark. */ +export const CustomBrand: Story = { + args: { + version: 'v6.0.0-alpha.1', + brand: My Company GraphQL Explorer, + }, + decorators: [ + Story => ( + + + + + + ), + ], +}; + /** GET selected with a mutation in the editor: Run disabled, method toggle highlighted. */ export const MutationBlockedOverGet: Story = { render: () => ( diff --git a/packages/graphiql-react/src/components/top-bar/top-bar.test.tsx b/packages/graphiql-react/src/components/top-bar/top-bar.test.tsx index 3b55e234498..3a75e11e87a 100644 --- a/packages/graphiql-react/src/components/top-bar/top-bar.test.tsx +++ b/packages/graphiql-react/src/components/top-bar/top-bar.test.tsx @@ -41,6 +41,14 @@ describe('TopBarView', () => { expect(screen.getByText('GraphiQL')).toBeInTheDocument(); }); + it('renders custom branding in place of the default wordmark', () => { + const { queryByText } = render( + My Company} />, + ); + expect(queryByText('My Company')).toBeInTheDocument(); + expect(queryByText('GraphiQL')).not.toBeInTheDocument(); + }); + it('renders the version pill when provided', () => { render(); expect(screen.getByText('v6.0.0-alpha.1')).toBeInTheDocument(); From 6d2a54dd87b66ad5ca1513ad30333b3274ea25d2 Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Sun, 12 Jul 2026 17:32:46 -0700 Subject: [PATCH 2/4] Remove the `GraphiQL.Toolbar` and `GraphiQL.Logo` slots Both slots are vestigial after the v6 redesign: the tab strip's prettify/merge/copy/save actions already cover the toolbar's default buttons, and branding now lives in the top bar. `GraphiQL.Toolbar` is gone outright with no deprecation period; use a plugin's `sessionActions` instead. `GraphiQL.Logo` is gone now that `` takes a `brand` prop for custom branding. `GraphiQL.Footer` is untouched. Also drops the now-dead `toolbar.additionalContent` / `toolbar.additionalComponent` `TypeError` guards, since they pointed embedders at the component being removed here, and the `.graphiql-toolbar` / `.graphiql-logo` CSS that only those slots used. Breaking change, major changeset included. --- .changeset/retire-toolbar-logo-slots.md | 5 ++ packages/graphiql/README.md | 13 ++-- packages/graphiql/src/GraphiQL.spec.tsx | 78 ++++++++-------------- packages/graphiql/src/GraphiQL.tsx | 43 +++--------- packages/graphiql/src/graphiql.css | 40 ----------- packages/graphiql/src/ui/index.ts | 2 - packages/graphiql/src/ui/logo.tsx | 21 ------ packages/graphiql/src/ui/toolbar.tsx | 89 ------------------------- 8 files changed, 48 insertions(+), 243 deletions(-) create mode 100644 .changeset/retire-toolbar-logo-slots.md delete mode 100644 packages/graphiql/src/ui/logo.tsx delete mode 100644 packages/graphiql/src/ui/toolbar.tsx diff --git a/.changeset/retire-toolbar-logo-slots.md b/.changeset/retire-toolbar-logo-slots.md new file mode 100644 index 00000000000..f6e9d9bad83 --- /dev/null +++ b/.changeset/retire-toolbar-logo-slots.md @@ -0,0 +1,5 @@ +--- +'graphiql': major +--- + +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 `` (or `` directly). `GraphiQL.Footer` is unchanged. See the migration guide for before/after examples. diff --git a/packages/graphiql/README.md b/packages/graphiql/README.md index 3495eb64ad2..82efa6b9889 100644 --- a/packages/graphiql/README.md +++ b/packages/graphiql/README.md @@ -132,14 +132,15 @@ For props documentation, see the Parts of the UI can be customized by passing children to the `GraphiQL` or the `GraphiQLInterface` component. -- ``: Replace the GraphiQL logo with your own. +- ``: Add a custom footer shown below the response editor. -- ``: Add a custom toolbar below the execution button. Pass - the empty `` if an empty toolbar is desired. Use the - components provided by `@graphiql/react` to create toolbar buttons with proper - styles. +Branding and toolbar customization moved off the children API in `graphiql@6`: -- ``: Add a custom footer shown below the response editor. +- Replace the top bar's default hexagon icon + "GraphiQL" wordmark with the + `brand` prop, e.g. ``. +- Add custom toolbar buttons through a plugin's `sessionActions`, which renders + into the tab strip alongside prettify/merge/copy/save. See the + [v6 migration guide](../../docs/migration/graphiql-6.0.0.md) for details. ### Plugins diff --git a/packages/graphiql/src/GraphiQL.spec.tsx b/packages/graphiql/src/GraphiQL.spec.tsx index 00d9db673d3..9f0d31042c6 100644 --- a/packages/graphiql/src/GraphiQL.spec.tsx +++ b/packages/graphiql/src/GraphiQL.spec.tsx @@ -231,6 +231,27 @@ describe('GraphiQL', () => { }); }); // default query + describe('brand', () => { + it('shows the default GraphiQL wordmark when brand is unset', async () => { + const { getByText } = render(); + + await waitFor(() => { + expect(getByText('GraphiQL')).toBeInTheDocument(); + }); + }); + + it('overrides the top bar branding with the brand prop', async () => { + const { getByText, queryByText } = render( + , + ); + + await waitFor(() => { + expect(getByText('My Company')).toBeInTheDocument(); + expect(queryByText('GraphiQL')).not.toBeInTheDocument(); + }); + }); + }); + // TODO: rewrite these plugin tests after plugin API has more structure describe('plugins', () => { it('displays correct plugin when visiblePlugin prop is used', async () => { @@ -627,7 +648,7 @@ describe('GraphiQL', () => { ); - const { container, queryByRole } = render( + const { container } = render( {myFragment}, ); @@ -636,14 +657,13 @@ describe('GraphiQL', () => { container.querySelector('.graphiql-container'), ).toBeInTheDocument(); expect( - container.querySelector('.graphiql-logo'), + container.querySelector('.graphiql-footer'), ).not.toBeInTheDocument(); - expect(queryByRole('toolbar')).not.toBeInTheDocument(); }); }); it('properly ignores non-override children components', async () => { - const { container, queryByRole } = render( + const { container } = render( , @@ -654,9 +674,8 @@ describe('GraphiQL', () => { container.querySelector('.graphiql-container'), ).toBeInTheDocument(); expect( - container.querySelector('.graphiql-logo'), + container.querySelector('.graphiql-footer'), ).not.toBeInTheDocument(); - expect(queryByRole('toolbar')).not.toBeInTheDocument(); }); }); @@ -668,7 +687,7 @@ describe('GraphiQL', () => { } } - const { container, queryByRole } = render( + const { container } = render( , @@ -679,43 +698,8 @@ describe('GraphiQL', () => { container.querySelector('.graphiql-container'), ).toBeInTheDocument(); expect( - container.querySelector('.graphiql-logo'), + container.querySelector('.graphiql-footer'), ).not.toBeInTheDocument(); - expect(queryByRole('toolbar')).not.toBeInTheDocument(); - }); - }); - - describe('GraphiQL.Logo', () => { - it('can be overridden using the exported type', async () => { - const { getByText } = render( - - My Exported Type Logo - , - ); - - await waitFor(() => { - expect(getByText('My Exported Type Logo')).toBeInTheDocument(); - }); - }); - }); - - describe('GraphiQL.Toolbar', () => { - it('can be overridden using the exported type', async () => { - const { container } = render( - - - {() => } - - , - ); - - await waitFor(() => { - expect( - container.querySelectorAll( - '[role="toolbar"] .graphiql-toolbar-button', - ), - ).toHaveLength(1); - }); }); }); @@ -739,7 +723,7 @@ describe('GraphiQL', () => { }); describe('tab strip actions', () => { - it('renders exactly one prettify, merge, copy, and save action, and no legacy toolbar or logo', async () => { + it('renders exactly one prettify, merge, copy, and save action', async () => { const { container } = render( {}} />, ); @@ -759,12 +743,6 @@ describe('GraphiQL', () => { expect( container.querySelectorAll('[aria-label="Save query"]'), ).toHaveLength(1); - expect( - container.querySelector('.graphiql-toolbar'), - ).not.toBeInTheDocument(); - expect( - container.querySelector('.graphiql-logo'), - ).not.toBeInTheDocument(); }); }); diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index c21f00b1189..355bf8f38cd 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -51,12 +51,7 @@ import { } from '@graphiql/plugin-doc-explorer'; import { QUERY_BUILDER_PLUGIN } from '@graphiql/plugin-query-builder'; import { collectionsPlugin } from '@graphiql/plugin-collections'; -import { - ActivityBar, - GraphiQLLogo, - GraphiQLToolbar, - GraphiQLFooter, -} from './ui'; +import { ActivityBar, GraphiQLFooter } from './ui'; const DEFAULT_PLUGINS = [ HISTORY_PLUGIN, @@ -104,22 +99,11 @@ const GraphiQL_: FC = ({ forcedTheme, confirmCloseTab, className, + brand, children, ...props }) => { - // @ts-expect-error -- Prop is removed - if (props.toolbar?.additionalContent) { - throw new TypeError( - 'The `toolbar.additionalContent` prop has been removed. Use render props on `GraphiQL.Toolbar` component instead.', - ); - } - // @ts-expect-error -- Prop is removed - if (props.toolbar?.additionalComponent) { - throw new TypeError( - 'The `toolbar.additionalComponent` prop has been removed. Use render props on `GraphiQL.Toolbar` component instead.', - ); - } // @ts-expect-error -- Prop is removed if (props.keyMap) { throw new TypeError( @@ -143,6 +127,7 @@ const GraphiQL_: FC = ({ forcedTheme, confirmCloseTab, className, + brand, }; const hasHistoryPlugin = plugins.includes(HISTORY_PLUGIN); const HistoryToUse = hasHistoryPlugin ? HistoryStore : Fragment; @@ -184,7 +169,8 @@ export interface GraphiQLInterfaceProps Pick< ComponentPropsWithoutRef, 'forcedTheme' | 'showPersistHeadersSettings' - > { + >, + Pick, 'brand'> { children?: ReactNode; /** * Set the default state for the editor tools. @@ -240,6 +226,7 @@ export const GraphiQLInterface: FC = ({ onEditHeaders, responseTooltip, showPersistHeadersSettings, + brand, }) => { const { addTab, @@ -336,22 +323,12 @@ export const GraphiQLInterface: FC = ({ storageKey: 'secondaryEditorFlex', }); - const { logo, toolbar, footer, children } = Children.toArray( - $children, - ).reduce<{ - logo?: ReactNode; - toolbar?: ReactNode; + const { footer, children } = Children.toArray($children).reduce<{ footer?: ReactNode; children: ReactNode[]; }>( (acc, curr) => { switch (getChildComponentType(curr)) { - case GraphiQL.Logo: - acc.logo = curr; - break; - case GraphiQL.Toolbar: - acc.toolbar = curr; - break; case GraphiQL.Footer: acc.footer = curr; break; @@ -413,7 +390,6 @@ export const GraphiQLInterface: FC = ({ ref={editorToolsFirstRef} > {hasMonaco ? : } - {toolbar}
@@ -463,7 +439,7 @@ export const GraphiQLInterface: FC = ({ ref={setContainerRef} className={cn('graphiql-container', className)} > - +
= ({ ) : null, )}
- {logo}
button { - flex-shrink: 0; -} - -/* The toolbar icons */ -.graphiql-toolbar-icon { - color: oklch(var(--fg-subtle)); - display: block; - height: calc(var(--toolbar-width) - (var(--px-8) * 2)); - width: calc(var(--toolbar-width) - (var(--px-8) * 2)); -} - /* The tab bar for editor tools */ .graphiql-container .graphiql-editor-tools { cursor: row-resize; @@ -226,7 +187,6 @@ button.graphiql-tab-strip-action { * setting the position of this to `relative` makes sure this element will * always be on top of any editors. */ -.graphiql-container .graphiql-toolbar, .graphiql-container .graphiql-editor-tools, .graphiql-container .graphiql-editor-tool { position: relative; diff --git a/packages/graphiql/src/ui/index.ts b/packages/graphiql/src/ui/index.ts index 44a71a06f13..4a629a1160f 100644 --- a/packages/graphiql/src/ui/index.ts +++ b/packages/graphiql/src/ui/index.ts @@ -1,6 +1,4 @@ export { ActivityBar } from './activity-bar'; export type { ActivityBarProps } from './activity-bar'; export { GraphiQLFooter } from './footer'; -export { GraphiQLLogo } from './logo'; export { ShortKeys } from './short-keys'; -export { GraphiQLToolbar } from './toolbar'; diff --git a/packages/graphiql/src/ui/logo.tsx b/packages/graphiql/src/ui/logo.tsx deleted file mode 100644 index dbee3f4c7e2..00000000000 --- a/packages/graphiql/src/ui/logo.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { FC, ReactNode } from 'react'; - -const defaultGraphiqlLogo = ( - - Graph - i - QL - -); - -// Configure the UI by providing this Component as a child of GraphiQL. -export const GraphiQLLogo: FC<{ children?: ReactNode }> = ({ - children = defaultGraphiqlLogo, -}) => { - return
{children}
; -}; diff --git a/packages/graphiql/src/ui/toolbar.tsx b/packages/graphiql/src/ui/toolbar.tsx deleted file mode 100644 index 295e7e2f728..00000000000 --- a/packages/graphiql/src/ui/toolbar.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import type { FC, ReactElement, ReactNode } from 'react'; -import { - CopyIcon, - KEY_MAP, - MergeIcon, - PrettifyIcon, - ToolbarButton, - useGraphiQLActions, -} from '@graphiql/react'; - -const DefaultToolbarRenderProps: FC<{ - prettify: ReactNode; - copy: ReactNode; - merge: ReactNode; -}> = ({ prettify, copy, merge }) => ( - <> - {prettify} - {merge} - {copy} - -); - -/** - * Configure the UI by providing this Component as a child of GraphiQL. - * Not rendered by default: the tab strip's action buttons cover prettify, - * merge, and copy out of the box. This remains available for embedders who - * want the standalone toolbar. - */ -export const GraphiQLToolbar: FC<{ - children?: typeof DefaultToolbarRenderProps | ReactNode; -}> = ({ children = DefaultToolbarRenderProps }) => { - const isRenderProp = typeof children === 'function'; - const { copyQuery, prettifyEditors, mergeQuery } = useGraphiQLActions(); - - if (!isRenderProp) { - return ( -
- {children as ReactElement} -
- ); - } - - const prettify = ( - - - ); - - const merge = ( - - - ); - - const copy = ( - - - ); - - const rendered = (children as typeof DefaultToolbarRenderProps)({ - prettify, - copy, - merge, - }) as ReactElement; - - return ( -
- {rendered} -
- ); -}; From 240f833c37b275ce7495ccb5c76f92a00d1e0c33 Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Sun, 12 Jul 2026 17:32:51 -0700 Subject: [PATCH 3/4] Document the toolbar/logo slot removal in the v6 migration guide Add a "Removed GraphiQL.Toolbar and GraphiQL.Logo" section with before/after snippets for both replacements (plugin sessionActions, TopBar brand prop), and update the overview/contents to mention it alongside the removed-hooks breaking change. --- docs/migration/graphiql-6.0.0.md | 104 ++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 8 deletions(-) diff --git a/docs/migration/graphiql-6.0.0.md b/docs/migration/graphiql-6.0.0.md index 7573f532841..74c2b1a5c0d 100644 --- a/docs/migration/graphiql-6.0.0.md +++ b/docs/migration/graphiql-6.0.0.md @@ -1,6 +1,6 @@ # Upgrading `graphiql` to `6.0.0` -GraphiQL 6 is a visual redesign built on a new OKLCH color system, plus a handful of mostly additive API changes: a `Transport` API that sits alongside the existing `Fetcher`, a settings dialog for theme/density/font-size, and two new first-party plugins (a visual query builder and operation collections) installed by default. The one breaking change is that the hooks deprecated in v5 are now removed, each with a drop-in replacement. See [discussion #4219](https://github.com/graphql/graphiql/discussions/4219) for the design background and to leave feedback. +GraphiQL 6 is a visual redesign built on a new OKLCH color system, plus a handful of mostly additive API changes: a `Transport` API that sits alongside the existing `Fetcher`, a settings dialog for theme/density/font-size, and two new first-party plugins (a visual query builder and operation collections) installed by default. The breaking changes are the removal of the hooks deprecated in v5 and the removal of the `GraphiQL.Toolbar` / `GraphiQL.Logo` composable slots, each with a drop-in replacement. See [discussion #4219](https://github.com/graphql/graphiql/discussions/4219) for the design background and to leave feedback. If something in your integration breaks that isn't covered here, please open an issue and we'll add it. @@ -9,18 +9,19 @@ If something in your integration breaks that isn't covered here, please open an 1. [Overview](#overview) 2. [CSS and retheming](#css-and-retheming) 3. [Removed hooks](#removed-hooks) -4. [New `transport` prop](#new-transport-prop) -5. [New first-party plugins](#new-first-party-plugins) -6. [`@graphiql/plugin-explorer` removal](#graphiqlplugin-explorer-removal) -7. [Theme, density, and font-size settings](#theme-density-and-font-size-settings) -8. [Considering for removal in v7](#considering-for-removal-in-v7) -9. [Other notes](#other-notes) +4. [Removed `GraphiQL.Toolbar` and `GraphiQL.Logo`](#removed-graphiqltoolbar-and-graphiqllogo) +5. [New `transport` prop](#new-transport-prop) +6. [New first-party plugins](#new-first-party-plugins) +7. [`@graphiql/plugin-explorer` removal](#graphiqlplugin-explorer-removal) +8. [Theme, density, and font-size settings](#theme-density-and-font-size-settings) +9. [Considering for removal in v7](#considering-for-removal-in-v7) +10. [Other notes](#other-notes) ## Overview Visually, v6 introduces a new OKLCH-based design-token system, restyles every primitive component (buttons, dialogs, tabs, dropdowns, tooltips), and reworks the app chrome around a top bar, an activity rail, and a side panel. Functionally, the biggest changes are a new `Transport` API for the network layer, the active operation following your cursor instead of only updating on run, and two new default-installed plugins: a visual query builder and operation collections. -Most of this is additive and requires no rewrite: the old `fetcher` prop and the old CSS variables keep working. The one breaking change to watch for is the removal of the hooks that were deprecated back in v5 (`useEditorContext`, `usePluginContext`, and the rest). Each has a direct replacement, covered below. Read the sections relevant to your setup and treat the rest as optional cleanup. +Most of this is additive and requires no rewrite: the old `fetcher` prop and the old CSS variables keep working. Two things to watch for: the hooks that were deprecated back in v5 (`useEditorContext`, `usePluginContext`, and the rest) are now removed, and the composable `GraphiQL.Toolbar` / `GraphiQL.Logo` children are gone in favor of a plugin `sessionActions` and a `brand` prop on the top bar. Each has a direct replacement, covered below. Read the sections relevant to your setup and treat the rest as optional cleanup. ## CSS and retheming @@ -195,6 +196,93 @@ const { addToHistory } = useHistoryActions(); See the [`@graphiql/react` README](../../packages/graphiql-react/README.md#available-stores) for the full list of available store selectors and actions. +## Removed `GraphiQL.Toolbar` and `GraphiQL.Logo` + +`` and `` are removed. Both were compound-component children you passed to `` to customize the editor toolbar and the corner branding; in v6 those render sites don't exist anymore (the toolbar's default prettify/merge/copy actions and the corner logo were already removed from the default render in a prior v6 change), so the slots had nowhere natural left to render. `` is unchanged and still works exactly as before. + +### `GraphiQL.Toolbar` → plugin `sessionActions` + +Custom editor actions now live in the tab strip, next to prettify/merge/copy/save, through a plugin's `sessionActions`: an always-mounted component every registered plugin can provide, regardless of whether that plugin's pane is visible. + +**Before:** + +```tsx +import { GraphiQL } from 'graphiql'; +import { ToolbarButton } from '@graphiql/react'; + +function App() { + return ( + + + {({ prettify, merge, copy }) => ( + <> + {prettify} + {merge} + {copy} + + + )} + + + ); +} +``` + +**After:** + +```tsx +import { GraphiQL } from 'graphiql'; +import { HISTORY_PLUGIN } from '@graphiql/plugin-history'; +import { QUERY_BUILDER_PLUGIN } from '@graphiql/plugin-query-builder'; +import { collectionsPlugin } from '@graphiql/plugin-collections'; +import { ToolbarButton, type GraphiQLPlugin } from '@graphiql/react'; + +const myActionsPlugin: GraphiQLPlugin = { + title: 'My Plugin', + icon: MyIcon, + content: MyPluginPanel, + sessionActions: () => ( + + ), +}; + +function App() { + return ( + + ); +} +``` + +Prettify, merge, and copy no longer need re-wiring: they're built into the tab strip by default. Passing the `plugins` prop replaces the default-installed plugin set (as it always has), so include the ones you still want (`HISTORY_PLUGIN`, `QUERY_BUILDER_PLUGIN`, `collectionsPlugin()`) alongside your own. + +### `GraphiQL.Logo` → the top bar's `brand` prop + +Branding is customized through a `brand` prop passed straight to `` (or to `` directly if you're composing your own layout with `GraphiQLInterface`). It accepts any `ReactNode` and replaces the default hexagon icon + "GraphiQL" wordmark; leave it unset to keep the default. + +**Before:** + +```tsx + + My Company + +``` + +**After:** + +```tsx + +``` + +`brand` isn't limited to text — pass any element, like your own logo image alongside a label. + ## New `transport` prop `@graphiql/toolkit` adds `createTransport`, which takes the same options as `createGraphiQLFetcher` and produces a `Transport`. Unlike `Fetcher`, a `Transport`'s response carries the real HTTP wire data: status code, headers, body, timing, and request/response sizes. `` accepts a new `transport` prop alongside the existing `fetcher` prop; the two are mutually exclusive at the type level. The old API still works, unchanged. From 17c786e355f0b4cababd0ca6a49c8ceb66063134 Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Sun, 12 Jul 2026 17:33:04 -0700 Subject: [PATCH 4/4] Migrate the vite-react-router example off GraphiQL.Toolbar/Logo Replace `` with the `brand` prop and rebuild the custom share-query button as a plugin with `sessionActions`, alongside the default history/query-builder/collections plugins it implicitly had before. --- .../app/routes/_index/graphiql.client.tsx | 58 +++++++++++-------- .../graphiql-vite-react-router/package.json | 3 + yarn.lock | 3 + 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/examples/graphiql-vite-react-router/app/routes/_index/graphiql.client.tsx b/examples/graphiql-vite-react-router/app/routes/_index/graphiql.client.tsx index f50c5f3fa0f..e2849822e68 100644 --- a/examples/graphiql-vite-react-router/app/routes/_index/graphiql.client.tsx +++ b/examples/graphiql-vite-react-router/app/routes/_index/graphiql.client.tsx @@ -1,35 +1,22 @@ import type { FC } from 'react'; import { GraphiQL } from 'graphiql'; import { createTransport } from '@graphiql/toolkit'; -import { ToolbarButton, useGraphiQL } from '@graphiql/react'; +import { + CopyIcon, + ToolbarButton, + useGraphiQL, + type GraphiQLPlugin, +} from '@graphiql/react'; +import { HISTORY_PLUGIN } from '@graphiql/plugin-history'; +import { QUERY_BUILDER_PLUGIN } from '@graphiql/plugin-query-builder'; +import { collectionsPlugin } from '@graphiql/plugin-collections'; import 'graphiql/setup-workers/esm.sh'; const transport = createTransport({ url: 'https://graphql.earthdata.nasa.gov/api', }); -export const graphiql = ( - - API Explorer - - {({ prettify, copy, merge }) => ( - <> - {prettify} - {copy} - {merge} -