Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/retire-toolbar-logo-slots.md
Original file line number Diff line number Diff line change
@@ -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 `<GraphiQL>` (or `<TopBar>` directly). `GraphiQL.Footer` is unchanged. See the migration guide for before/after examples.
104 changes: 96 additions & 8 deletions docs/migration/graphiql-6.0.0.md
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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

Expand Down Expand Up @@ -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`

`<GraphiQL.Toolbar>` and `<GraphiQL.Logo>` are removed. Both were compound-component children you passed to `<GraphiQL>` 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. `<GraphiQL.Footer>` 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 (
<GraphiQL fetcher={fetcher}>
<GraphiQL.Toolbar>
{({ prettify, merge, copy }) => (
<>
{prettify}
{merge}
{copy}
<ToolbarButton label="My custom action" onClick={onClick} />
</>
)}
</GraphiQL.Toolbar>
</GraphiQL>
);
}
```

**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: () => (
<ToolbarButton label="My custom action" onClick={onClick} />
),
};

function App() {
return (
<GraphiQL
fetcher={fetcher}
plugins={[
HISTORY_PLUGIN,
QUERY_BUILDER_PLUGIN,
collectionsPlugin(),
myActionsPlugin,
]}
/>
);
}
```

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 `<GraphiQL>` (or to `<TopBar>` 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
<GraphiQL fetcher={fetcher}>
<GraphiQL.Logo>My Company</GraphiQL.Logo>
</GraphiQL>
```

**After:**

```tsx
<GraphiQL fetcher={fetcher} brand="My Company" />
```

`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. `<GraphiQL>` 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = (
<GraphiQL
dangerouslyAssumeSchemaIsValid
defaultEditorToolsVisibility="variables"
transport={transport}
isHeadersEditorEnabled={false}
>
<GraphiQL.Logo>API Explorer</GraphiQL.Logo>
<GraphiQL.Toolbar>
{({ prettify, copy, merge }) => (
<>
{prettify}
{copy}
{merge}
<Button />
</>
)}
</GraphiQL.Toolbar>
</GraphiQL>
);

const Button: FC = () => {
const ShareButton: FC = () => {
const { queryEditor, variableEditor } = useGraphiQL(state => ({
queryEditor: state.queryEditor,
variableEditor: state.variableEditor,
Expand Down Expand Up @@ -60,3 +47,28 @@ const Button: FC = () => {
</ToolbarButton>
);
};

const sharePlugin: GraphiQLPlugin = {
title: 'Share Explorer Query',
icon: CopyIcon,
content: () => (
<p>Copies a link to the current query and variables to your clipboard.</p>
),
sessionActions: ShareButton,
};

export const graphiql = (
<GraphiQL
dangerouslyAssumeSchemaIsValid
defaultEditorToolsVisibility="variables"
transport={transport}
isHeadersEditorEnabled={false}
brand="API Explorer"
plugins={[
HISTORY_PLUGIN,
QUERY_BUILDER_PLUGIN,
collectionsPlugin(),
sharePlugin,
]}
/>
);
3 changes: 3 additions & 0 deletions examples/graphiql-vite-react-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"start": "react-router-serve dist/server/index.js"
},
"dependencies": {
"@graphiql/plugin-collections": "^0.1.0-alpha.0",
"@graphiql/plugin-history": "^0.4.2",
"@graphiql/plugin-query-builder": "^0.1.0-alpha.0",
"@graphiql/react": "^0.37.7",
"@react-router/fs-routes": "^7",
"@react-router/node": "^7",
Expand Down
23 changes: 19 additions & 4 deletions packages/graphiql-react/src/components/top-bar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<TopBarProps> = ({ version }) => {
export const TopBar: FC<TopBarProps> = ({ version, brand }) => {
const { run, setTransportMethod, setOperationName } = useGraphiQLActions();
const isFetching = useGraphiQL(state => state.isFetching);
const transport = useGraphiQL(state => state.transport);
Expand All @@ -42,6 +47,7 @@ export const TopBar: FC<TopBarProps> = ({ version }) => {
return (
<TopBarView
version={version}
brand={brand}
isFetching={isFetching}
url={url}
method={method}
Expand All @@ -60,6 +66,7 @@ export const TopBar: FC<TopBarProps> = ({ version }) => {

export type TopBarViewProps = {
version?: string;
brand?: ReactNode;
isFetching: boolean;
url: string;
method: HttpMethod;
Expand All @@ -79,6 +86,7 @@ export type TopBarViewProps = {

export const TopBarView: FC<TopBarViewProps> = ({
version,
brand,
isFetching,
url,
method,
Expand Down Expand Up @@ -202,8 +210,15 @@ export const TopBarView: FC<TopBarViewProps> = ({
return (
<header className="graphiql-top-bar" role="banner">
<div className="graphiql-top-bar-brand">
<GraphQLLogoIcon className="graphiql-top-bar-logo" aria-hidden="true" />
<span className="graphiql-top-bar-wordmark">GraphiQL</span>
{brand ?? (
<>
<GraphQLLogoIcon
className="graphiql-top-bar-logo"
aria-hidden="true"
/>
<span className="graphiql-top-bar-wordmark">GraphiQL</span>
</>
)}
{version && <span className="graphiql-top-bar-version">{version}</span>}
</div>

Expand Down
17 changes: 17 additions & 0 deletions packages/graphiql-react/src/components/top-bar/top-bar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: <span style={{ fontWeight: 600 }}>My Company GraphQL Explorer</span>,
},
decorators: [
Story => (
<Tooltip.Provider>
<GraphiQLProvider transport={postOnlyTransport}>
<Story />
</GraphiQLProvider>
</Tooltip.Provider>
),
],
};

/** GET selected with a mutation in the editor: Run disabled, method toggle highlighted. */
export const MutationBlockedOverGet: Story = {
render: () => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ describe('TopBarView', () => {
expect(screen.getByText('GraphiQL')).toBeInTheDocument();
});

it('renders custom branding in place of the default wordmark', () => {
const { queryByText } = render(
<TopBarView {...DEFAULTS} brand={<span>My Company</span>} />,
);
expect(queryByText('My Company')).toBeInTheDocument();
expect(queryByText('GraphiQL')).not.toBeInTheDocument();
});

it('renders the version pill when provided', () => {
render(<TopBarView {...DEFAULTS} version="v6.0.0-alpha.1" />);
expect(screen.getByText('v6.0.0-alpha.1')).toBeInTheDocument();
Expand Down
13 changes: 7 additions & 6 deletions packages/graphiql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

- `<GraphiQL.Logo>`: Replace the GraphiQL logo with your own.
- `<GraphiQL.Footer>`: Add a custom footer shown below the response editor.

- `<GraphiQL.Toolbar>`: Add a custom toolbar below the execution button. Pass
the empty `<GraphiQL.Toolbar />` 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`:

- `<GraphiQL.Footer>`: 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. `<GraphiQL brand="My Company" />`.
- 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

Expand Down
Loading
Loading