---
product: "AG Studio"
title: "Modes & Layout"
description: "AG Studio has two main modes - view and edit. Edit mode allows for the construction of reports with the drag-and-drop builder, whilst view mode presents the report for consumption, with the editing controls hidden."
framework: react
version: "3.0.0"
related:
    - title: "Theming"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/theming/"
    - title: "Theme Builder"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/theme-builder/"
    - title: "Localisation"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/localisation/"
    - title: "State"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/state/"
    - title: "Undo & Redo"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/undo-redo/"
    - title: "Exporting"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/exporting/"
    - title: "Figma Design System"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/figma-design-system/"
llms: "https://www.ag-grid.com/studio/archive/3.0.0/llms.txt"
---

# Modes & Layout

AG Studio has two main modes - view and edit. Edit mode allows for the construction of reports with the drag-and-drop builder, whilst view mode presents the report for consumption, with the editing controls hidden.

## Modes

#### Changing Mode

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgStudio, AgStudioRef } from "ag-studio-react";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  enableStudioDevValidations,
} from "ag-studio";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const StudioExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [data, setData] = useState<AgDataSourcesDefinition | AgDataEngine>();
  const initialState = useMemo<AgReportState>(() => {
    return {
      pages: [
        {
          id: "a",
          widgets: {
            "1": {
              type: "grid",
              dataMapping: {
                cols: [
                  { id: "medals.country" },
                  { id: "medals.sport" },
                  { id: "medals.gold", aggregation: "sum" },
                  { id: "medals.silver", aggregation: "sum" },
                  { id: "medals.bronze", aggregation: "sum" },
                  { id: "medals.total", aggregation: "sum" },
                ],
              },
            },
            "2": {
              type: "column-chart-grouped",
              dataMapping: {
                categoryKey: [{ id: "medals.country" }],
                valueKey: [
                  { id: "medals.gold", aggregation: "sum" },
                  { id: "medals.silver", aggregation: "sum" },
                  { id: "medals.bronze", aggregation: "sum" },
                ],
                tooltipKey: [],
              },
            },
          },
          widgetLayout: {
            "1": {
              xTrack: 0,
              yTrack: 0,
              xSpan: 24,
              ySpan: 16,
            },
            "2": {
              xTrack: 0,
              yTrack: 16,
              xSpan: 24,
              ySpan: 16,
            },
          },
        },
      ],
      selectedPageId: "a",
    };
  }, []);
  const [mode, setMode] = useState<AgStudioMode>("edit");

  const onApiReady = useCallback((params: AgStudioApiReadyEvent) => {
    fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data: any[]) => setData({ sources: [{ id: "medals", data }] }));
  }, []);

  const toggleMode = useCallback(() => {
    const currentMode = mode;
    const newMode = currentMode === "edit" ? "view" : "edit";
    setMode(newMode);
  }, [mode]);

  return (
    <div style={containerStyle}>
      <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
        <div className="example-controls">
          <div className="controls-row">
            <button id="toggleMode" onClick={toggleMode}>
              Switch to {mode === "edit" ? "View" : "Edit"} Mode
            </button>
          </div>
        </div>

        <AgStudio
          style={studioStyle}
          className="my-studio-container"
          data={data}
          initialState={initialState}
          mode={mode}
          onApiReady={onApiReady}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <StudioExample />
  </StrictMode>,
);
```

[Live example: Changing Mode](https://www.ag-grid.com/studio/archive/3.0.0/examples/modes-layout/changing-mode/reactFunctionalTs/)

The mode can be changed via the `mode` property.

By default, view mode hides all editing controls. Setting `enableFilterEditingInViewMode` relaxes this for filters only, so Page and Widget [Filters](https://www.ag-grid.com/studio/archive/3.0.0/react/filters/#filters-panel) can still be added and removed in view mode.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | `AgStudioMode` | `'view'` | Which mode Studio is in. Changing this only changes what is shown and what is editable: the active state is neither saved nor restored on a mode change, and carries across the switch unchanged. The undo and redo history also carries across by default; set `history.onModeChange` to discard it instead. |
| `enableFilterEditingInViewMode` | `boolean` | `false` | Allows filters to be added to, and removed from, the filters panel while in view mode. When `false`, filters can only be added and removed in edit mode. Studio does not store filters added this way. Listen to `onStateUpdated` and persist the state yourself if it needs to survive a reload. |

## Layout

The `layout` property controls the grid that widgets are placed on, and the space the page occupies.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `layout` | `Partial<AgPageLayoutState>` |  | Default layout styling. |

### Layout Properties

#### Layout Properties

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgStudio, AgStudioRef } from "ag-studio-react";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgPageLayoutState,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  enableStudioDevValidations,
} from "ag-studio";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const StudioExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [data, setData] = useState<AgDataSourcesDefinition | AgDataEngine>();
  const initialState = useMemo<AgReportState>(() => {
    return {
      pages: [
        {
          id: "a",
          widgets: {
            "1": {
              type: "grid",
              dataMapping: {
                cols: [
                  { id: "medals.country" },
                  { id: "medals.sport" },
                  { id: "medals.gold", aggregation: "sum" },
                  { id: "medals.silver", aggregation: "sum" },
                  { id: "medals.bronze", aggregation: "sum" },
                  { id: "medals.total", aggregation: "sum" },
                ],
              },
            },
            "2": {
              type: "column-chart-grouped",
              dataMapping: {
                categoryKey: [{ id: "medals.country" }],
                valueKey: [
                  { id: "medals.gold", aggregation: "sum" },
                  { id: "medals.silver", aggregation: "sum" },
                  { id: "medals.bronze", aggregation: "sum" },
                ],
                tooltipKey: [],
              },
            },
          },
          widgetLayout: {
            "1": {
              xTrack: 0,
              yTrack: 0,
              xSpan: 4,
              ySpan: 4,
            },
            "2": {
              xTrack: 0,
              yTrack: 4,
              xSpan: 4,
              ySpan: 4,
            },
          },
        },
      ],
      selectedPageId: "a",
      panels: {
        filters: {
          collapsed: true,
        },
        data: {
          collapsed: true,
        },
      },
    };
  }, []);
  const layout = useMemo<Partial<AgPageLayoutState>>(() => {
    return {
      columns: 4,
      rowHeight: 50,
    };
  }, []);

  const onApiReady = useCallback((params: AgStudioApiReadyEvent) => {
    fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data: any[]) => setData({ sources: [{ id: "medals", data }] }));
  }, []);

  return (
    <div style={containerStyle}>
      <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
        <AgStudio
          style={studioStyle}
          className="my-studio-container"
          data={data}
          initialState={initialState}
          mode={"edit"}
          layout={layout}
          onApiReady={onApiReady}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <StudioExample />
  </StrictMode>,
);
```

[Live example: Layout Properties](https://www.ag-grid.com/studio/archive/3.0.0/examples/modes-layout/layout-properties/reactFunctionalTs/)

The default layout setup can be overridden via the `layout` property. The example above adjusts the default number of columns and the row height in the layout. This affects the widget moving and resizing behaviour.

```jsx
const layout = {
    columns: 4,
    rowHeight: 50,
};

<AgStudio layout={layout} />
```

### Page Dimensions

Page Dimensions define the space your report should fit into and how it behaves when the browser window or device size changes.

```jsx
const layout = {
    minWidth: 800,
    maxWidth: 1200,
    height: 600,
};

<AgStudio layout={layout} />
```

### Width

Width settings help keep the layout readable and well-proportioned across different screen sizes.

Min Width defaults to 720px. It defines the smallest width the page can shrink to before horizontal scrolling is needed. In the example below, a large minimum width is set, so a horizontal scrollbar appears when the viewport is narrower than the minimum width.

#### Min Width

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgStudio, AgStudioRef } from "ag-studio-react";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgPanelConfig,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  enableStudioDevValidations,
} from "ag-studio";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const StudioExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [data, setData] = useState<AgDataSourcesDefinition | AgDataEngine>();
  const initialState = useMemo<AgReportState>(() => {
    return {
      pages: [
        {
          id: "a",
          layout: {
            minWidth: 1500,
          },
          widgets: {
            "gold-medals": {
              type: "value",
              dataMapping: {
                value: [{ id: "medals.gold", aggregation: "sum" }],
              },
              format: {
                caption: {
                  enabled: true,
                  text: "Gold Medals",
                },
              },
            },
            "silver-medals": {
              type: "value",
              dataMapping: {
                value: [{ id: "medals.silver", aggregation: "sum" }],
              },
              format: {
                caption: {
                  enabled: true,
                  text: "Silver Medals",
                },
              },
            },
            "bronze-medals": {
              type: "value",
              dataMapping: {
                value: [{ id: "medals.bronze", aggregation: "sum" }],
              },
              format: {
                caption: {
                  enabled: true,
                  text: "Bronze Medals",
                },
              },
            },
          },
          widgetLayout: {
            "gold-medals": {
              xTrack: 0,
              yTrack: 0,
              xSpan: 8,
              ySpan: 8,
            },
            "silver-medals": {
              xTrack: 8,
              yTrack: 0,
              xSpan: 8,
              ySpan: 8,
            },
            "bronze-medals": {
              xTrack: 16,
              yTrack: 0,
              xSpan: 8,
              ySpan: 8,
            },
          },
        },
      ],
      selectedPageId: "a",
    };
  }, []);
  const panels = useMemo<AgPanelConfig>(() => {
    return {
      edit: {
        right: ["edit", "data"],
      },
    };
  }, []);

  const onApiReady = useCallback((params: AgStudioApiReadyEvent) => {
    fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data: any[]) => setData({ sources: [{ id: "medals", data }] }));
  }, []);

  return (
    <div style={containerStyle}>
      <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
        <AgStudio
          style={studioStyle}
          className="my-studio-container"
          data={data}
          initialState={initialState}
          mode={"view"}
          panels={panels}
          onApiReady={onApiReady}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <StudioExample />
  </StrictMode>,
);
```

[Live example: Min Width](https://www.ag-grid.com/studio/archive/3.0.0/examples/modes-layout/min-width/reactFunctionalTs/)

Max Width is optional and can be left as Auto. When left as Auto, the dashboard can continue expanding beyond the minimum width as the viewport grows.

If Max Width is set, the dashboard stops growing once it reaches that width and remains centred on the screen. In the example below, a fixed maximum width is applied, so the dashboard stops expanding and centres within the available space.

#### Max Width

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgStudio, AgStudioRef } from "ag-studio-react";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgPanelConfig,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  enableStudioDevValidations,
} from "ag-studio";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const StudioExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [data, setData] = useState<AgDataSourcesDefinition | AgDataEngine>();
  const initialState = useMemo<AgReportState>(() => {
    return {
      pages: [
        {
          id: "a",
          layout: {
            maxWidth: 300,
          },
          widgets: {
            "gold-medals": {
              type: "value",
              dataMapping: {
                value: [{ id: "medals.gold", aggregation: "sum" }],
              },
              format: {
                caption: {
                  enabled: true,
                  text: "Gold Medals",
                },
              },
            },
            "silver-medals": {
              type: "value",
              dataMapping: {
                value: [{ id: "medals.silver", aggregation: "sum" }],
              },
              format: {
                caption: {
                  enabled: true,
                  text: "Silver Medals",
                },
              },
            },
            "bronze-medals": {
              type: "value",
              dataMapping: {
                value: [{ id: "medals.bronze", aggregation: "sum" }],
              },
              format: {
                caption: {
                  enabled: true,
                  text: "Bronze Medals",
                },
              },
            },
          },
          widgetLayout: {
            "gold-medals": {
              xTrack: 0,
              yTrack: 0,
              xSpan: 8,
              ySpan: 8,
            },
            "silver-medals": {
              xTrack: 8,
              yTrack: 0,
              xSpan: 8,
              ySpan: 8,
            },
            "bronze-medals": {
              xTrack: 16,
              yTrack: 0,
              xSpan: 8,
              ySpan: 8,
            },
          },
        },
      ],
      selectedPageId: "a",
    };
  }, []);
  const panels = useMemo<AgPanelConfig>(() => {
    return {
      edit: {
        right: ["edit", "data"],
      },
    };
  }, []);

  const onApiReady = useCallback((params: AgStudioApiReadyEvent) => {
    fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data: any[]) => setData({ sources: [{ id: "medals", data }] }));
  }, []);

  return (
    <div style={containerStyle}>
      <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
        <AgStudio
          style={studioStyle}
          className="my-studio-container"
          data={data}
          initialState={initialState}
          mode={"view"}
          panels={panels}
          onApiReady={onApiReady}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <StudioExample />
  </StrictMode>,
);
```

[Live example: Max Width](https://www.ag-grid.com/studio/archive/3.0.0/examples/modes-layout/max-width/reactFunctionalTs/)

This means:

- A required Min Width sets the minimum readable size.
- An optional Max Width controls how wide the dashboard is allowed to grow.

### Height

Auto Height allows the page to grow as Widgets are added. This works well for dashboards that may expand over time, or reports where content scrolls vertically.

Fixed Height locks the page to a specific height, like a slide or poster. This works well when vertical boundaries need to be fixed for a consistent, contained view, especially for dashboards designed to fit on a single screen.

#### Fixed Height

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgStudio, AgStudioRef } from "ag-studio-react";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgPanelConfig,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  enableStudioDevValidations,
} from "ag-studio";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const StudioExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [data, setData] = useState<AgDataSourcesDefinition | AgDataEngine>();
  const initialState = useMemo<AgReportState>(() => {
    return {
      pages: [
        {
          id: "a",
          layout: {
            height: 88,
          },
          widgets: {
            "gold-medals": {
              type: "value",
              dataMapping: {
                value: [{ id: "medals.gold", aggregation: "sum" }],
              },
              format: {
                caption: {
                  enabled: true,
                  text: "Gold Medals",
                },
              },
            },
            "silver-medals": {
              type: "value",
              dataMapping: {
                value: [{ id: "medals.silver", aggregation: "sum" }],
              },
              format: {
                caption: {
                  enabled: true,
                  text: "Silver Medals",
                },
              },
            },
            "bronze-medals": {
              type: "value",
              dataMapping: {
                value: [{ id: "medals.bronze", aggregation: "sum" }],
              },
              format: {
                caption: {
                  enabled: true,
                  text: "Bronze Medals",
                },
              },
            },
          },
          widgetLayout: {
            "gold-medals": {
              xTrack: 0,
              yTrack: 0,
              xSpan: 8,
              ySpan: 5,
            },
            "silver-medals": {
              xTrack: 8,
              yTrack: 0,
              xSpan: 8,
              ySpan: 5,
            },
            "bronze-medals": {
              xTrack: 16,
              yTrack: 0,
              xSpan: 8,
              ySpan: 5,
            },
          },
        },
      ],
      selectedPageId: "a",
    };
  }, []);
  const panels = useMemo<AgPanelConfig>(() => {
    return {
      edit: {
        right: ["edit", "data"],
      },
    };
  }, []);

  const onApiReady = useCallback((params: AgStudioApiReadyEvent) => {
    fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data: any[]) => setData({ sources: [{ id: "medals", data }] }));
  }, []);

  return (
    <div style={containerStyle}>
      <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
        <AgStudio
          style={studioStyle}
          className="my-studio-container"
          data={data}
          initialState={initialState}
          mode={"view"}
          panels={panels}
          onApiReady={onApiReady}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <StudioExample />
  </StrictMode>,
);
```

[Live example: Fixed Height](https://www.ag-grid.com/studio/archive/3.0.0/examples/modes-layout/fixed-height/reactFunctionalTs/)

> **Note**
>
> When using Fixed Height, set it to be a multiple of the layout `rowHeight` plus double the layout `pagePadding` to avoid additional padding at the top and bottom of the layout. `rowHeight` defaults to the `studioCanvasRowHeight` theme variable, which is `16` in the default theme. `pagePadding` defaults to `widgetPadding` if not defined, which in turn defaults to the `studioWidgetPadding` theme variable, which is `4` in the default theme

## Panels

Studio has four different panels that can be displayed depending on the mode:

- AI Panel (`'ai'`) - Used for the [AI Feature](https://www.ag-grid.com/studio/archive/3.0.0/react/ai/).
- Filters Panel (`'filters'`) - Contains page filters, widget filters, cross filters, and filters from filter widgets.
- Edit Panel (`'edit'`) - Changes function based on the UI selection to display editing controls.
- Data Panel (`'data'`) - Displays the fields available in the data.

### Panel Configuration

Which panels are displayed, and on which side, can be configured for both view mode and edit mode.

#### Configuring Panels

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgStudio, AgStudioRef } from "ag-studio-react";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgPanelConfig,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  enableStudioDevValidations,
} from "ag-studio";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const StudioExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [data, setData] = useState<AgDataSourcesDefinition | AgDataEngine>();
  const initialState = useMemo<AgReportState>(() => {
    return {
      pages: [
        {
          id: "a",
          widgets: {
            "1": {
              type: "grid",
              dataMapping: {
                cols: [
                  { id: "medals.country" },
                  { id: "medals.sport" },
                  { id: "medals.gold", aggregation: "sum" },
                  { id: "medals.silver", aggregation: "sum" },
                  { id: "medals.bronze", aggregation: "sum" },
                  { id: "medals.total", aggregation: "sum" },
                ],
              },
            },
            "2": {
              type: "column-chart-grouped",
              dataMapping: {
                categoryKey: [{ id: "medals.country" }],
                valueKey: [
                  { id: "medals.gold", aggregation: "sum" },
                  { id: "medals.silver", aggregation: "sum" },
                  { id: "medals.bronze", aggregation: "sum" },
                ],
                tooltipKey: [],
              },
            },
          },
          widgetLayout: {
            "1": {
              xTrack: 0,
              yTrack: 0,
              xSpan: 24,
              ySpan: 16,
            },
            "2": {
              xTrack: 0,
              yTrack: 16,
              xSpan: 24,
              ySpan: 16,
            },
          },
        },
      ],
      selectedPageId: "a",
      panels: {
        filters: {
          collapsed: true,
        },
      },
    };
  }, []);
  const [mode, setMode] = useState<AgStudioMode>("edit");
  const panels = useMemo<AgPanelConfig>(() => {
    return {
      edit: {
        left: ["filters"],
        right: ["edit", "data"],
      },
      view: {
        left: [],
      },
    };
  }, []);

  const onApiReady = useCallback((params: AgStudioApiReadyEvent) => {
    fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data: any[]) => setData({ sources: [{ id: "medals", data }] }));
  }, []);

  const toggleMode = useCallback(() => {
    const currentMode = mode;
    const newMode = currentMode === "edit" ? "view" : "edit";
    setMode(newMode);
  }, [mode]);

  return (
    <div style={containerStyle}>
      <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
        <div className="example-controls">
          <div className="controls-row">
            <button id="toggleMode" onClick={toggleMode}>
              Switch to {mode === "edit" ? "View" : "Edit"} Mode
            </button>
          </div>
        </div>

        <AgStudio
          style={studioStyle}
          className="my-studio-container"
          data={data}
          initialState={initialState}
          mode={mode}
          panels={panels}
          onApiReady={onApiReady}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <StudioExample />
  </StrictMode>,
);
```

[Live example: Configuring Panels](https://www.ag-grid.com/studio/archive/3.0.0/examples/modes-layout/configuring-panels/reactFunctionalTs/)

The example above demonstrates displaying the Filters Panel on the left-hand side in edit mode (and collapsed by default via [Initial State](https://www.ag-grid.com/studio/archive/3.0.0/react/state/)). In view mode, the Filters Panel is hidden completely.

```jsx
const panels = {
    edit: {
        left: ['filters'],
        right: ['edit', 'data']
    },
    view: {
        left: [],
    },
};

<AgStudio panels={panels} />
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `panels` | `AgPanelConfig` |  | Configure which panels are displayed and on which side. |

Note that panels controlling editing functionality are only available in edit mode.

### Panel Content

#### Customising Panel Content

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgStudio, AgStudioRef } from "ag-studio-react";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgPageConfig,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  enableStudioDevValidations,
} from "ag-studio";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const StudioExample = () => {
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [data, setData] = useState<AgDataSourcesDefinition | AgDataEngine>();
  const initialState = useMemo<AgReportState>(() => {
    return {
      pages: [
        {
          id: "a",
          widgets: {
            "1": {
              type: "grid",
              dataMapping: {
                cols: [
                  { id: "medals.country" },
                  { id: "medals.sport" },
                  { id: "medals.gold", aggregation: "sum" },
                  { id: "medals.silver", aggregation: "sum" },
                  { id: "medals.bronze", aggregation: "sum" },
                  { id: "medals.total", aggregation: "sum" },
                ],
              },
            },
            "2": {
              type: "column-chart-grouped",
              dataMapping: {
                categoryKey: [{ id: "medals.country" }],
                valueKey: [
                  { id: "medals.gold", aggregation: "sum" },
                  { id: "medals.silver", aggregation: "sum" },
                  { id: "medals.bronze", aggregation: "sum" },
                ],
                tooltipKey: [],
              },
            },
          },
          widgetLayout: {
            "1": {
              xTrack: 0,
              yTrack: 0,
              xSpan: 24,
              ySpan: 16,
            },
            "2": {
              xTrack: 0,
              yTrack: 16,
              xSpan: 24,
              ySpan: 16,
            },
          },
        },
      ],
      selectedPageId: "a",
      panels: {
        filters: {
          collapsed: true,
        },
        data: {
          collapsed: true,
        },
      },
    };
  }, []);
  const page = useMemo<
    AgPageConfig | ((config: AgPageConfig) => AgPageConfig)
  >(() => {
    return ({ setupForm }: AgPageConfig) => {
      const newConfig: AgPageConfig = {
        // only show the first item from the page setup form
        setupForm: Array.isArray(setupForm)
          ? setupForm.slice(0, 1)
          : { ...setupForm, items: setupForm.items.slice(0, 1) },
      };
      return newConfig;
    };
  }, []);

  const onApiReady = useCallback((params: AgStudioApiReadyEvent) => {
    fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data: any[]) => setData({ sources: [{ id: "medals", data }] }));
  }, []);

  return (
    <div style={containerStyle}>
      <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
        <AgStudio
          style={studioStyle}
          className="my-studio-container"
          data={data}
          initialState={initialState}
          mode={"edit"}
          page={page}
          onApiReady={onApiReady}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <StudioExample />
  </StrictMode>,
);
```

[Live example: Customising Panel Content](https://www.ag-grid.com/studio/archive/3.0.0/examples/modes-layout/panel-content/reactFunctionalTs/)

The content of the edit panel can be customised in multiple ways. The example above demonstrates configuring the Page tab to only show the first item (page background).

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `page` | `AgPageConfig \| ((config: AgPageConfig) => AgPageConfig)` |  | Configure page (e.g. page setup form in page tab of edit panel). |

The widget content of the panels is controlled by [Available Widgets](https://www.ag-grid.com/studio/archive/3.0.0/react/widget-configuration/#available-widgets) and [Widget Overrides](https://www.ag-grid.com/studio/archive/3.0.0/react/widget-configuration/#widget-overrides).

The page setup form items are set up in a similar way to the widget [Form Grouping Items](https://www.ag-grid.com/studio/archive/3.0.0/react/custom-widgets-form/#form-grouping-items) and [Form Input Items](https://www.ag-grid.com/studio/archive/3.0.0/react/custom-widgets-form/#form-input-items), but only a subset of input items are supported.
