Customization

Text Mode

Text mode lets the builder switch between the visual query UI and a SQL editor view of the same query.

Enable built-in text mode
import '@vojtechportes/react-query-builder/styles.css';
import React, { useState } from 'react';
import {
Builder,
type DenormalizedQuery,
type IBuilderFieldProps,
} from '@vojtechportes/react-query-builder';
const fields: IBuilderFieldProps[] = [
{
field: 'CUSTOMER_COUNTRY',
label: 'Customer country',
type: 'LIST',
operators: ['EQUAL', 'NOT_EQUAL'],
value: [
{ value: 'CZ', label: 'Czech Republic' },
{ value: 'SK', label: 'Slovakia' },
],
},
{
field: 'ORDER_TOTAL',
label: 'Order total',
type: 'NUMBER',
operators: ['LARGER_EQUAL', 'BETWEEN'],
},
];
export const TextModeBuilder = () => {
const [data, setData] = useState<DenormalizedQuery>(initialData);
return (
<Builder
fields={fields}
data={data}
textMode
onChange={setData}
/>
);
};

Current scope

  • Text mode currently uses SQL as the editable text format.
  • It requires singleRootGroup to stay enabled. If singleRootGroup is false, text mode is unavailable and the builder stays in visual mode.
  • When text mode is enabled, modifierless groups are normalized to AND groups so the query can round-trip through SQL.

Opening mode

Use defaultMode to choose whether the builder opens in the visual builder or in text mode.

Open directly in text mode
import '@vojtechportes/react-query-builder/styles.css';
<Builder
fields={fields}
data={data}
textMode
defaultMode="text"
onChange={setData}
/>;
// defaultMode only takes effect when textMode is enabled.
Explicit textMode config
import '@vojtechportes/react-query-builder/styles.css';
<Builder
fields={fields}
data={data}
textMode={{
format: 'SQL',
defaultMode: 'builder',
}}
defaultMode="text"
onChange={setData}
/>;
// textMode can be either:
// - true
// - { format?: 'SQL'; defaultMode?: 'builder' | 'text' }
//
// If both are provided, the top-level defaultMode prop wins.

Validation

  • Missing brackets, quotes, commas, and other SQL syntax mistakes are highlighted directly in the editor.
  • Unknown fields are highlighted on the field token.
  • Unsupported operators for a field are highlighted on the operator token.
  • Invalid LIST values are highlighted on the invalid value token.
  • Invalid MULTI_LIST values are highlighted on the invalid value token.
  • Read-only negation changes are rejected after parsing and shown as below-editor semantic errors.

Invalid text behavior

  • Invalid text stays local to the text editor until the SQL becomes valid again.
  • The last valid builder query is preserved while the user is fixing text-mode errors.
  • onChange is fired only after a valid parse and successful semantic validation.

History behavior

  • Valid text edits are committed into builder history, so they can be undone and redone.
  • Invalid intermediate text is not committed into builder state or history.

Choosing an editor

  • Choose the built-in editor when you want lightweight SQL editing, built-in validation, and no extra dependencies.
  • Choose Monaco when locked or targeted read-only query segments must stay protected in text mode, or when you want a more advanced editor experience.

Built-in editor vs Monaco

  • Built-in text mode Included in the core package, uses the default TextModeEditor, and supports SQL formatting, syntax highlighting, syntax validation, and semantic validation without extra dependencies.
  • Built-in editor limitation Locked rules, locked groups, and targeted read-only queries are blocked before entering text mode there, because the basic editor cannot preserve protected query segments safely after freeform text edits.
  • Monaco text mode Optional advanced editor integration exposed from @vojtechportes/react-query-builder/monaco. It preserves locked and targeted read-only query segments by rendering them as protected ranges.
  • Monaco protected behavior Protected SQL fragments are dimmed, protected from edits, and expose their lock explanation on hover. The packaged Monaco light and dark themes use the same query-builder palette roles as the built-in editor for SQL tokens. An explicit Builder colorScheme switches between them without remounting the editor. Because Monaco's standalone theme service is global, the latest mounted packaged editor, or the editor whose scheme changes most recently, wins across all mounted Monaco editors.
  • Localized read-only protection Rule field, operator, and value segments can be protected inline, while read-only negation is additionally enforced semantically and reported below the editor when changed.
  • Group negation validation When allowGroupNegation=, the text editor rejects group-level NOT (...) expressions and highlights the offending NOT token, while still allowing operator-level negation such as NOT IN or IS NOT NULL.
  • Monaco packaging monaco-editor is an optional peer dependency. Consumers only need to install it when they actually want the Monaco editor.

Using Monaco text mode

Monaco uses the same public @vojtechportes/react-query-builder/styles.css import as the default builder. Import it once; the Monaco subpackage does not ship a second stylesheet.

Monaco text mode with default components
import '@vojtechportes/react-query-builder/styles.css';
import React, { useState } from 'react';
import {
Builder,
type DenormalizedQuery,
} from '@vojtechportes/react-query-builder';
import { createMonacoComponents } from '@vojtechportes/react-query-builder/monaco';
const components = createMonacoComponents({});
export const MonacoTextModeBuilder = () => {
const [data, setData] = useState<DenormalizedQuery>(initialData);
return (
<Builder
fields={fields}
data={data}
textMode
defaultMode="text"
components={components}
onChange={setData}
/>
);
};
Compose Monaco with MUI
import '@vojtechportes/react-query-builder/styles.css';
import { Builder } from '@vojtechportes/react-query-builder';
import { components as muiComponents } from '@vojtechportes/react-query-builder/mui/v9';
import { createMonacoComponents } from '@vojtechportes/react-query-builder/monaco';
const components = createMonacoComponents(muiComponents);
<Builder
fields={fields}
data={data}
textMode
components={components}
onChange={setData}
/>;
// The same pattern works with @vojtechportes/react-query-builder/antd/v6.
Compose Monaco with ANTD
import '@vojtechportes/react-query-builder/styles.css';
import { Builder } from '@vojtechportes/react-query-builder';
import { components as antdComponents } from '@vojtechportes/react-query-builder/antd/v6';
import { createMonacoComponents } from '@vojtechportes/react-query-builder/monaco';
const components = createMonacoComponents(antdComponents);
<Builder
fields={fields}
data={data}
textMode
components={components}
onChange={setData}
/>;

Monaco styling hooks

  • Use public editor, color, spacing, radius, and shadow CSS variables for supported presentation changes.
  • .rqb-monaco-text-mode-editor is the stable class on the Monaco surface when an application needs a wrapper-scoped selector.
  • Generated rqb_[local]_[hash] CSS Module classes are private and must not be used as selectors.

Text-mode strings

Text-mode labels and messages are part of the regular strings override surface.

Custom text-mode strings
import '@vojtechportes/react-query-builder/styles.css';
import { strings } from '@vojtechportes/react-query-builder';
<Builder
fields={fields}
data={data}
textMode
strings={{
...strings,
textMode: {
...strings.textMode,
toggleToText: 'Switch to SQL mode',
toggleToBuilder: 'Switch to visual builder',
syntaxError: 'SQL syntax error',
locksUnsupported: 'Locked rules and groups are not supported in this text editor mode.',
lockedRangesHover: 'This SQL fragment is locked and cannot be edited.',
},
}}
onChange={setData}
/>;
  • strings.textMode.toggleToText customizes the button label for entering text mode.
  • strings.textMode.toggleToBuilder customizes the button label for returning to the visual builder.
  • strings.textMode.syntaxError customizes the syntax error prefix.
  • strings.textMode.locksUnsupported customizes the built-in alert shown when the basic editor cannot open a locked or targeted read-only query.
  • strings.textMode.lockedRangesHover customizes the hover message shown for protected Monaco ranges.
  • strings.textMode.sql customizes SQL parser and syntax-validation messages such as missing brackets, missing quotes, missing keywords, and unexpected tokens.

Installing Monaco

Monaco peer dependency
npm install monaco-editor
Locks and Monaco
The built-in text editor blocks locked and targeted read-only queries. Monaco is the intended path when protected query segments need to remain editable only around their unlocked ranges.
API reference
© Vojtěch Václav Porteš 2026 - All library contents are available under the MIT license.
Loading privacy preferences...