Mantine Json Tree

Logo

@gfazioli/mantine-json-tree

A Mantine extension component that renders interactive JSON trees with syntax highlighting, collapsible nodes, in-place value editing, copy-to-clipboard, and configurable expansion depth.

What's new

  • Editable values — click a string, number or boolean to edit it in place (editable, onChange)
  • Circular references are detected and marked instead of crashing the component
  • Search with text highlight, auto-expand, and filtered tree view (withSearch)
  • Redesigned toolbar with key count badge, global copy, search toggle, and modern icons
  • Paper wrapper with withBorder for bordered container look
  • Root name customization (rootName prop)
  • Global copy button to copy entire JSON to clipboard (withCopyAll)
  • Key count badge showing total keys/items next to title (withKeyCountBadge)
  • Dark mode support with automatic color adaptation
  • Line numbers display (showLineNumbers)
  • Path tooltip on hover (showPathOnHover)
  • Max height with scrollable container (maxHeight)
  • Controlled expand/collapse state (expanded, onExpandedChange)
  • onExpand and onCollapse callbacks for individual node toggling
  • Keyboard copy: Ctrl+C / Cmd+C copies the focused node when withCopyToClipboard is enabled
  • Responsive size prop via Mantine breakpoint objects (CSS-native, no re-renders)

Installation

yarn add @gfazioli/mantine-json-tree

After installation import package styles at the root of your application:

import '@gfazioli/mantine-json-tree/styles.css';

You can import styles within a layer @layer mantine-json-tree by importing @gfazioli/mantine-json-tree/styles.layer.css file.

import '@gfazioli/mantine-json-tree/styles.layer.css';

Usage

The JsonTree is interactive JSON tree viewer component built with Mantine's Tree component. Features collapsible nodes, syntax highlighting with type-specific colors, copy-to-clipboard functionality, item count badges, configurable expansion depth, and smooth animations. Perfect for debugging API responses, exploring complex data structures, and developer tools.

My JSON
9 keys
  • root:{
    • name:"John Doe"
    • age:30
    • isAdmin:false
    • courses:[...]
      3
    • wife:null
    • onClick:[Function: onClick]
    • address:{...}
      3
    • action:{...}
      2
    • projects:[...]
      2
Size
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { data } from './data';

function Demo() {
  return <JsonTree title="My JSON" size="sm" withBorder withKeyCountBadge withExpandAll withCopyAll withSearch withCopyToClipboard showIndentGuides showItemsCount showLineNumbers showPathOnHover data={data} maxDepth={1} defaultExpanded/>;
}

Editing Values

Set editable to let a reader change a value in place. Click a string or a number to open an inline editor — Enter commits, Escape cancels, moving focus commits — or click a boolean to toggle it outright, since it has exactly one other state. With a row focused, Enter opens its editor too, so the whole flow works from the keyboard without adding a tab stop per value.

JsonTree is controlled while editing: it never keeps its own copy of your data. onChange hands you the next value, and it only takes effect once you feed it back through data.

data is typed unknown, so onChange hands the next value back as unknown too. Editing a value never changes the shape of the object — a string stays a string, a number a number — so casting it back to your own type is safe:

const [data, setData] = useState<Profile>(initial);

<JsonTree data={data} editable onChange={(next) => setData(next as Profile)} />

The original object is never mutated. Only the spine down to the edited node is rebuilt, so every Date, Map, Set, RegExp, BigInt, function and React element elsewhere in the tree keeps its identity — a structuredClone or a JSON round-trip would flatten or destroy all of them.

profile.json
  • root:{
    • name:"Jamie Chen"
    • age:34
    • isAdmin:false
    • createdAt:2024-01-15T10:30:00.000Z
    • address:{
      • city:"Anytown"
      • zip:"12345"
    • tags:[
      • 0:"react"
      • 1:"mantine"

Click a value to edit it. Enter commits, Escape cancels.

import { useState } from 'react';
import { JsonTree, JsonTreeChange } from "@gfazioli/mantine-json-tree";
import { Paper, Stack, Text } from '@mantine/core';

interface Profile {
  name: string;
  age: number;
  isAdmin: boolean;
  createdAt: Date;
  address: { city: string; zip: string };
  tags: string[];
}

function Demo() {
  const [data, setData] = useState<Profile>({
    name: 'Jamie Chen',
    age: 34,
    isAdmin: false,
    createdAt: new Date('2024-01-15T10:30:00Z'),
    address: {
      city: 'Anytown',
      zip: '12345',
    },
    tags: ['react', 'mantine'],
  });

  const [lastChange, setLastChange] = useState<JsonTreeChange | null>(null);

  return (
    <Stack>
      <Paper withBorder>
        <JsonTree
          data={data}
          title="profile.json"
          defaultExpanded
          maxDepth={-1}
          editable
          onChange={(next, change) => {
            // editing a value never changes the shape, so the cast is safe
            setData(next as Profile);
            setLastChange(change);
          }}
          // zip stays read-only even though its type is editable
          isEditable={({ key }) => key !== 'zip'}
          validate={({ type, value }) =>
            type === 'string' && String(value).trim() === '' ? 'Cannot be empty' : null
          }
        />
      </Paper>

      <Text size="sm" c="dimmed">
        {lastChange
          ? `${lastChange.path}: ${JSON.stringify(lastChange.previousValue)} → ${JSON.stringify(lastChange.value)}`
          : 'Click a value to edit it. Enter commits, Escape cancels.'}
      </Text>
    </Stack>
  );
}

What can be edited

editableTypes defaults to ['string', 'number', 'boolean']; anything outside it stays read-only. Two kinds of node can never be edited, whatever you put in that list:

  • Map and Set entries. They are displayed under a synthetic key — a Map key can be any value at all, including an object — so there is no address to write back to.
  • Function properties expanded with displayFunctions="as-object". Those properties belong to a synthetic object that does not exist in your data.

isEditable gates individual nodes, and validate rejects a value by returning a message:

<JsonTree
  data={data}
  editable
  onChange={setData}
  isEditable={({ path }) => path !== 'root.id'}
  validate={({ value }) => (String(value).trim() === '' ? 'Cannot be empty' : null)}
/>

Addressing a node

onChange receives a change object carrying both ways of naming the node:

  • path — the display label, e.g. root.address.city. It is not unique: { 'a.b': 1 } and { a: { b: 1 } } both produce root.a.b, and an array index reads the same as a numeric object key.
  • pathSegments — the real address, one step per level, with object keys as strings and array indices as numbers. This is what the write is performed against, so an edit always lands on the node you clicked.

Use path for display and logging; use pathSegments whenever you need to act on the node.

Search

Enable withSearch to add a search toggle in the toolbar. When active, the tree filters to show only branches with matching keys or values. Direct matches get an amber background highlight, and the matching text portion is highlighted inline. Parent nodes are preserved for context. Clearing the search restores the previous expand state.

API Response
7 keys
  • root:{
    • id:"usr_7k2m9p"
    • email:"jamie@example.com"
    • name:"Jamie Chen"
    • role:"admin"
    • verified:true
    • created_at:"2026-03-10T23:42:00Z"
    • metadata:{
      • login_count:142
      • last_ip:"203.0.113.42"
      • preferences:{...}
import { JsonTree } from '@gfazioli/mantine-json-tree';

function Demo() {
  return (
    <JsonTree
      data={data}
      title="API Response"
      rootName="root"
      withBorder
      withSearch
      withExpandAll
      withKeyCountBadge
      withCopyAll
      defaultExpanded
    />
  );
}

Customizing the search input

Use searchInputProps to forward any prop to the internal TextInputclassNames, styles, vars, variant, radius, size, leftSection, and so on. Because Mantine applies styles inline, this works without specificity hacks or !important.

Custom search input
  • root:{
    • user:{
      • id:"usr_42"
      • name:"Jamie"
      • role:"admin"
    • metadata:{
      • theme:"dark"
      • notifications:true
import { JsonTree } from '@gfazioli/mantine-json-tree';

function Demo() {
  return (
    <JsonTree
      data={data}
      title="Custom search input"
      withBorder
      withSearch
      defaultExpanded
      searchInputProps={{
        radius: 'xl',
        variant: 'filled',
        placeholder: 'Look up keys or values…',
        styles: {
          input: {
            backgroundColor: 'var(--mantine-color-dark-7)',
            borderColor: 'var(--mantine-color-dark-4)',
            color: 'var(--mantine-color-gray-0)',
          },
        },
      }}
    />
  );
}

Copy to Clipboard

Both per-node and global copy buttons provide visual feedback — the icon briefly changes to a green checkmark after a successful copy.

The global copy button (withCopyAll) copies the raw JSON data without the rootName wrapper. The rootName is a display-only label and is not included in the copied output. This makes the copied JSON directly usable in code and API tools.

What lands on the clipboard is what the tree shows. JSON.stringify alone cannot express everything the tree renders — it returns undefined for functions, symbols and undefined, and throws on BigInt and on any value holding a reference cycle — so those fall back to the rendered form rather than copying the literal text undefined or silently doing nothing.

Root Name

Use the rootName prop to customize the label of the root node. Defaults to "root". This is useful for API responses, config files, or when displaying multiple trees with different root labels. Note that rootName is display-only — it does not affect the copied JSON output:

Custom Root Name
3 keys
  • response:{
    • id:1
    • name:"Alice"
    • role:"admin"
Array Root Name
3 items
  • items:[
    • 0:1
    • 1:2
    • 2:3
import { JsonTree } from '@gfazioli/mantine-json-tree';
import { Stack } from '@mantine/core';

function Demo() {
  return (
    <Stack>
      <JsonTree
        data={{ id: 1, name: 'Alice', role: 'admin' }}
        rootName="response"
        title="Custom Root Name"
        withBorder
        withKeyCountBadge
        defaultExpanded
      />
      <JsonTree
        data={[1, 2, 3]}
        rootName="items"
        title="Array Root Name"
        withBorder
        withKeyCountBadge
        defaultExpanded
      />
    </Stack>
  );
}

Syntax Highlighting values

Below is an example of syntax highlighting for different JSON value types: strings, numbers, booleans, nulls, objects, and arrays. Each type is displayed in a distinct color for better readability.

Simple string
  • root:"Hello, World!"
Number value
  • root:42
Boolean value (true)
  • root:true
Boolean value (false)
  • root:false
Null value
  • root:null
Object value
  • root:{
    • key1:"value1"
    • key2:123
    • key3:false
    • key4:null
Array value
  • root:[
    • 0:"string"
    • 1:456
    • 2:true
    • 3:null
    • 4:{
      • nestedKey:"nestedValue"
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { Paper, Stack } from '@mantine/core';

function Demo() {
  return (
    <Stack>
      <Paper withBorder>
        <JsonTree data="Hello, World!" defaultExpanded title="Simple string" />
      </Paper>
      <Paper withBorder>
        <JsonTree data={42} defaultExpanded title="Number value" />
      </Paper>
      <Paper withBorder>
        <JsonTree data={true} defaultExpanded title="Boolean value (true)" />
      </Paper>
      <Paper withBorder>
        <JsonTree data={false} defaultExpanded title="Boolean value (false)" />
      </Paper>
      <Paper withBorder>
        <JsonTree data={null} defaultExpanded title="Null value" />
      </Paper>
      <Paper withBorder>
        <JsonTree data={{ key1: 'value1', key2: 123, key3: false, key4: null }} defaultExpanded title="Object value" />
      </Paper>
      <Paper withBorder>
        <JsonTree data={['string', 456, true, null, { nestedKey: 'nestedValue' }]} defaultExpanded title="Array value" />
      </Paper>
    </Stack>
  );
}

Special Value Types

JsonTree supports modern JavaScript types beyond standard JSON primitives. These include Date objects, special numeric values (NaN, Infinity), BigInt for large integers, Symbols, RegExp patterns, and ES6 collections like Map and Set. Each type is displayed with distinct syntax highlighting and formatting:

  • Date: Displayed in ISO 8601 format (e.g., 2024-01-15T10:30:00.000Z)
  • NaN / Infinity: Special numeric values shown with their standard JavaScript representation
  • BigInt: Large integers displayed with the n suffix (e.g., 9007199254740991n)
  • Symbol: Unique identifiers shown with their description (e.g., Symbol(unique))
  • RegExp: Regular expressions displayed with their pattern and flags (e.g., /pattern/gi)
  • Map: Key-value collections that are expandable to show their entries
  • Set: Unique value collections that are expandable to show their elements
  • React Elements: React components displayed with their component name (e.g., <Loader />)

React elements are recognised by their internal $$typeof marker, so ordinary data is never mistaken for one. An object such as { type: 'text', props: { label: 'Name' } } — a common shape in form-builder and low-code JSON — is rendered as a regular, expandable object.

All colors can be customized using CSS variables for each type.

special-types.json
  • root:{
    • reactLoader:<@mantine/core/Loader />
    • reactButton:<button />
    • htmlDiv:<div />
    • createdAt:2024-01-15T10:30:00.000Z
    • lastModified:2024-06-20T14:45:30.000Z
    • notANumber:NaN
    • positiveInfinity:Infinity
    • negativeInfinity:-Infinity
    • bigInteger:9007199254740991n
    • userId:123456789n
    • globalSymbol:Symbol(app.config)
    • registryKey:Symbol(app.registry)
    • emailPattern:/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/g
    • phonePattern:/\d{3}-\d{3}-\d{4}/
    • userMap:{
      • [0] user1:{...}
        2
      • [1] user2:{...}
        2
      • [2] 123:"numeric key example"
    • tags:{
      • 0:"javascript"
      • 1:"typescript"
      • 2:"react"
    • uniqueNumbers:{
      • 0:1
      • 1:2
      • 2:3
      • 3:4
      • 4:5
    • metadata:{
      • timestamp:2024-12-25T00:00:00.000Z
      • categories:{...}
        2
      • config:{...}
        2
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { Loader, Paper } from '@mantine/core';

function Demo() {
  const specialTypes = {
    // React components
    reactLoader: <Loader size="xs" />,
    reactButton: <button type="button">Click me</button>,
    htmlDiv: <div>Hello World</div>,
    // Date objects - displayed in ISO format
    createdAt: new Date('2024-01-15T10:30:00Z'),
    lastModified: new Date('2024-06-20T14:45:30Z'),

    // Special numeric values
    notANumber: NaN,
    positiveInfinity: Infinity,
    negativeInfinity: -Infinity,

    // BigInt for large integers
    bigInteger: BigInt('9007199254740991'),
    userId: BigInt(123456789),

    // Symbols for unique identifiers
    globalSymbol: Symbol.for('app.config'),
    registryKey: Symbol.for('app.registry'),

    // Regular expressions
    emailPattern: /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/g,
    phonePattern: new RegExp('\\d{3}-\\d{3}-\\d{4}'),

    // Map collections (key-value pairs)
    userMap: new Map([
      ['user1', { name: 'Alice', role: 'admin' }],
      ['user2', { name: 'Bob', role: 'user' }],
      [123, 'numeric key example'],
    ]),

    // Set collections (unique values)
    tags: new Set(['javascript', 'typescript', 'react']),
    uniqueNumbers: new Set([1, 2, 3, 4, 5]),

    // Nested combinations
    metadata: {
      timestamp: new Date('2024-12-25T00:00:00Z'),
      categories: new Set(['frontend', 'backend']),
      config: new Map([
        ['version', '1.0.0'],
        ['environment', 'production'],
      ]),
    },
  };

  return (
    <Paper withBorder>
      <JsonTree
        data={specialTypes}
        title="special-types.json"
        defaultExpanded
        withExpandAll
        showItemsCount
      />
    </Paper>
  );
}

Circular References

Object graphs that point back at themselves are everywhere in real debugging sessions — a child holding a reference to its parent, a linked list, a cached entity pointing at the store that owns it. JsonTree walks the current branch and stops as soon as a value reappears on its own ancestor chain, rendering it as [Circular] rather than recursing until the stack overflows.

Only the ancestor chain is tracked, never every value already visited. A value referenced from two different branches is shared, not circular, so it keeps expanding normally in both places — as the config object does below.

graph.json
  • root:{
    • id:"root"
    • config:{
      • theme:"dark"
      • locale:"en"
    • child:{
      • id:"child"
      • config:{
        • theme:"dark"
        • locale:"en"
      • parent:[Circular]
    • self:[Circular]
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { Paper } from '@mantine/core';

function Demo() {
  // A node graph: every child points back at its parent, and a shared
  // "config" object is referenced from two different branches.
  const config = { theme: 'dark', locale: 'en' };

  const root: any = {
    id: 'root',
    config,
    child: {
      id: 'child',
      config,
    },
  };

  root.self = root;
  root.child.parent = root;

  return (
    <Paper withBorder>
      <JsonTree
        data={root}
        title="graph.json"
        defaultExpanded
        maxDepth={-1}
        withExpandAll
        showItemsCount
      />
    </Paper>
  );
}

Indent Guides

Display vertical lines to visualize nesting levels, similar to Visual Studio Code. The guides use 5 distinct colors that cycle for deeper nesting levels.

JSON with Indent Guides
  • root:{
    • name:"John Doe"
    • age:30
    • isAdmin:false
    • courses:[
      • 0:"html"
      • 1:"css"
      • 2:"js"
    • wife:null
    • onClick:[Function: onClick]
    • address:{
      • street:"123 Main St"
      • city:"Anytown"
      • zip:"12345"
    • action:{
      • type:"click"
      • payload:undefined
    • projects:[
      • 0:{
        • name:"Project A"
        • status:"completed"
      • 1:{
        • name:"Project B"
        • status:"in progress"
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { data } from './data';

function Demo() {
  return (
    <JsonTree 
      data={data} 
      defaultExpanded 
      maxDepth={-1}
      showIndentGuides
      title="JSON with Indent Guides"
    />
  );
}

Line Numbers

Display line numbers alongside each node, similar to code editors. This is useful for referencing specific parts of the JSON structure.

JSON with Line Numbers
  • root:{
    • name:"John Doe"
    • age:30
    • isAdmin:false
    • courses:[
      • 0:"html"
      • 1:"css"
      • 2:"js"
    • wife:null
    • onClick:[Function: onClick]
    • address:{
      • street:"123 Main St"
      • city:"Anytown"
      • zip:"12345"
    • action:{
      • type:"click"
      • payload:undefined
    • projects:[
      • 0:{
        • name:"Project A"
        • status:"completed"
      • 1:{
        • name:"Project B"
        • status:"in progress"
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { data } from './data';

function Demo() {
  return (
    <JsonTree
      data={data}
      defaultExpanded
      maxDepth={-1}
      showLineNumbers
      title="JSON with Line Numbers"
    />
  );
}

Sticky Header

You may enable sticky headers for better context when scrolling through large JSON structures. When enabled, the header of each expanded node remains visible at the top of the scrollable area as you navigate through its child elements. You can also set an offset to accommodate fixed headers in your layout.

data.json
  • root:{
    • name:"John Doe"
    • age:30
    • isAdmin:false
    • courses:[
      • 0:"html"
      • 1:"css"
      • 2:"js"
    • wife:null
    • onClick:[Function: onClick]
    • address:{
      • street:"123 Main St"
      • city:"Anytown"
      • zip:"12345"
    • action:{
      • type:"click"
      • payload:undefined
    • projects:[
      • 0:{...}
      • 1:{...}
Sticky header offset
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { data } from './data';

function Demo() {
  return (
    <JsonTree stickyHeader
      data={data}
      title="data.json"
      defaultExpanded
      withExpandAll
      maxHeight={300}
      styles={{
        header: { backgroundColor: 'var(--mantine-color-default)' },
      }}
    />
  );
}

Icons

You can customize the expand/collapse icons used in the JsonTree component by providing your own icons via the expandControlIcon and collapseControlIcon props. The default icons are simple chevrons, but you can replace them with any React component or icon of your choice.

Note: If only the expandControlIcon is provided, the component will automatically use it for both expand and collapse states, by rotating (90deg) it accordingly. If only the collapseControlIcon is provided, the default expand icon will be used. Of course, you can provide both icons for complete customization.

Only Expand Icons

demo.json
  • root:{...}

Only Collapse Icons

demo.json
  • root:{...}

Both Expand and Collapse Icons

demo.json
  • root:{...}
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { Paper, SimpleGrid, Text, Title } from '@mantine/core';
import { data } from './data';

function Demo() {
  return (
    <SimpleGrid cols={3}>
      <Paper withBorder p="md">
        <Title order={4}>Only Expand Icons</Title>
        <JsonTree
          title="demo.json"
          showIndentGuides
          data={data}
          expandControlIcon={<span>👉</span>}
        />
      </Paper>
      <Paper withBorder p="md">
        <Title order={4}>Only Collapse Icons</Title>
        <JsonTree
          title="demo.json"
          showIndentGuides
          data={data}
          collapseControlIcon={<span>👇</span>}
        />
      </Paper>
      <Paper withBorder p="md">
        <Title order={4}>Both Expand and Collapse Icons</Title>
        <JsonTree
          title="demo.json"
          showIndentGuides
          data={data}
          expandControlIcon={
            <Text fz={24} c="red">
              ⊕
            </Text>
          }
          collapseControlIcon={<Text fz={24}>⊖</Text>}
        />
      </Paper>
    </SimpleGrid>
  );
}

Function Display

By default, functions in JSON data are displayed as strings (e.g., [Function: name]). You can control how functions are handled using the displayFunctions prop:

  • as-string (default): Display functions as formatted strings showing their name
  • hide: Completely omit functions from the tree
  • as-object: Treat functions as objects and display their properties
as-string
  • root:{
    • name:"UserProfile"
    • age:25
    • onClick:[Function: onClick]
    • calculate:[Function: calculate]
    • methods:{
      • fetchData:[Function: fetchData]
      • process:[Function: process]
    • data:[
      • 0:1
      • 1:2
      • 2:3
    • isActive:true
hide
  • root:{
    • name:"UserProfile"
    • age:25
    • methods:[object Object]
    • data:[
      • 0:1
      • 1:2
      • 2:3
    • isActive:true
as-object
  • root:{
    • name:"UserProfile"
    • age:25
    • onClick:{
      • length:0
      • name:"onClick"
      • arguments:null
      • caller:null
      • prototype:[object Object]
    • calculate:{
      • length:2
      • name:"calculate"
    • methods:{
      • fetchData:{...}
      • process:{...}
    • data:[
      • 0:1
      • 1:2
      • 2:3
    • isActive:true
import { JsonTree } from '@gfazioli/mantine-json-tree';
import { Code, Paper, Stack } from '@mantine/core';

const dataWithFunctions = {
  name: 'UserProfile',
  age: 25,
  onClick: function handleClick() {
    console.log('clicked');
  },
  calculate: (a: number, b: number) => a + b,
  methods: {
    async fetchData() {
      return 'data';
    },
    process: function process(value: string) {
      return value.toUpperCase();
    },
  },
  data: [1, 2, 3],
  isActive: true,
};

function Demo() {
  return (
    <Stack>
      <Paper withBorder p="sm">
        <Code>as-string</Code>
        <JsonTree data={dataWithFunctions} defaultExpanded displayFunctions="as-string" mb="md" />
      </Paper>
      <Paper withBorder p="sm">
        <Code>hide</Code>
        <JsonTree data={dataWithFunctions} defaultExpanded displayFunctions="hide" mb="md" />
      </Paper>
      <Paper withBorder p="sm">
        <Code>as-object</Code>
        <JsonTree data={dataWithFunctions} defaultExpanded displayFunctions="as-object" />
      </Paper>
    </Stack>
  );
}

Callbacks

Use the onNodeClick callback to handle clicks on any node in the tree, and the onCopy callback to react when a value is copied to clipboard. Both callbacks provide the relevant data for integration with your application logic.

Click a node or copy a value
  • root:{
    • name:"John Doe"
    • age:30
    • isAdmin:false
    • courses:[...]
    • wife:null
    • onClick:[Function: onClick]
    • address:{...}
    • action:{...}
    • projects:[...]
import { useState } from 'react';
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { Code, Stack, Text } from "@mantine/core";
import { data } from './data';

function Demo() {
  const [clicked, setClicked] = useState<{ path: string; value: unknown } | null>(null);
  const [copied, setCopied] = useState<string | null>(null);

  return (
    <Stack>
      <JsonTree
        data={data}
        defaultExpanded
        maxDepth={1}
        withCopyToClipboard
        title="Click a node or copy a value"
        onNodeClick={(path, value) => setClicked({ path, value })}
        onCopy={(copy) => setCopied(copy)}
      />

      {clicked && (
        <Text size="sm">
          Clicked: <Code>{clicked.path}</Code>
        </Text>
      )}

      {copied && (
        <Text size="sm">
          Copied: <Code>{copied.length > 50 ? `${copied.slice(0, 50)}…` : copied}</Code>
        </Text>
      )}
    </Stack>
  );
}

Keyboard Navigation

JsonTree inherits full keyboard navigation from Mantine's Tree component:

  • Arrow Up/Down: Navigate between nodes
  • Arrow Right: Expand a collapsed node or move to first child
  • Arrow Left: Collapse an expanded node or move to parent
  • Space: Toggle node expansion
  • Enter: Open the inline editor for the focused row (when editable is enabled). Editable values add no tab stop of their own, so a large tree stays one stop, as Mantine's Tree intends
  • Ctrl+C / Cmd+C: Copy the focused node's value to clipboard (when withCopyToClipboard is enabled). With no node focused it falls back to the root, and it never intercepts the shortcut while a form control inside the component — such as the search input — holds focus.

Path Tooltip

Set showPathOnHover to display the full JSON path in a tooltip when hovering over any node. This is useful for identifying the exact path to a value in deeply nested structures. You can customize the tooltip behavior with the tooltipProps prop, which accepts all Tooltip props except label and children.

Hover over any node to see its path
  • root:{
    • name:"John Doe"
    • age:30
    • isAdmin:false
    • courses:[
      • 0:"html"
      • 1:"css"
      • 2:"js"
    • wife:null
    • onClick:[Function: onClick]
    • address:{
      • street:"123 Main St"
      • city:"Anytown"
      • zip:"12345"
    • action:{
      • type:"click"
      • payload:undefined
    • projects:[
      • 0:{
        • name:"Project A"
        • status:"completed"
      • 1:{
        • name:"Project B"
        • status:"in progress"
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { data } from './data';

function Demo() {
  return (
    <JsonTree
      data={data}
      defaultExpanded
      maxDepth={-1}
      showPathOnHover
      title="Hover over any node to see its path"
    />
  );
}

Max Height

Use the maxHeight prop to limit the tree height and enable scrolling. This is useful for embedding the tree in layouts with limited vertical space.

  • root:{
    • name:"John Doe"
    • age:30
    • isAdmin:false
    • courses:[
      • 0:"html"
      • 1:"css"
      • 2:"js"
    • wife:null
    • onClick:[Function: onClick]
    • address:{
      • street:"123 Main St"
      • city:"Anytown"
      • zip:"12345"
    • action:{
      • type:"click"
      • payload:undefined
    • projects:[
      • 0:{
        • name:"Project A"
        • status:"completed"
      • 1:{
        • name:"Project B"
        • status:"in progress"
Max height
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { data } from './data';

function Demo() {
  return <JsonTree maxHeight={300} showIndentGuides data={data} defaultExpanded maxDepth={-1} />;
}

Controlled Expand State

Use the expanded and onExpandedChange props for controlled expand/collapse state. The onExpand and onCollapse callbacks fire for individual node toggling:

const [expanded, setExpanded] = useState<string[]>([
  'root',
  'root.settings',
]);

<JsonTree
  data={data}
  expanded={expanded}
  onExpandedChange={setExpanded}
  onExpand={(path) => console.log('Expanded:', path)}
  onCollapse={(path) => console.log('Collapsed:', path)}
/>;

Responsive size

The size prop supports responsive values using Mantine breakpoint objects. This allows you to set different font sizes based on the viewport width, using CSS media queries (no JavaScript re-renders):

<JsonTree data={data} size={{ base: 'xs', sm: 'sm', lg: 'md' }} />

Style the component with classNames

You can style the JsonTree component using the classNames prop to target specific inner elements. This allows for granular customization of the component's appearance.

demo.json
  • root:{
    • name:"John Doe"
    • age:30
    • isAdmin:false
    • courses:[...]
      3
    • wife:null
    • onClick:[Function: onClick]
    • address:{...}
      3
    • action:{...}
      2
    • projects:[...]
      2
import { JsonTree } from "@gfazioli/mantine-json-tree";
import { data } from './data';
import classes from './JsonTree.module.css';

function Demo() {
  return (
    <JsonTree
      classNames={classes}
      title="demo.json"
      showIndentGuides
      defaultExpanded
      showItemsCount
      maxDepth={1}
      data={data}
    />
  );
}

Styles API

JsonTree supports Styles API, you can add styles to any inner element of the component with classNames prop. Follow Styles API documentation to learn more.

You can customize the appearance of the JsonTree component using the Styles API. This allows you to modify styles for various parts of the component, such as keys, values, brackets, and more.

demo.json
9 keys
  • data:{
    • name:"John Doe"
    • age:30
    • isAdmin:false
    • courses:[...]
      3
    • wife:null
    • onClick:[Function: onClick]
    • address:{...}
      3
    • action:{...}
      2
    • projects:[...]
      2

Component Styles API

Hover over selectors to highlight corresponding elements

/*
 * Hover over selectors to apply outline styles
 *
 */