⚠️ Warning: This library is in active development and is NOT ready for production use. This project is subject to breaking changes and may undergo significant modifications. Please review the CHANGELOG.md for the latest updates and breaking changes. Use at your own risk.
📖 Live Documentation & Interactive Playground: Explore the full interactive documentation with live examples at react-custom-syntax.trugraph.io or react-custom-syntax.vercel.app
A lightweight, customizable React syntax editor with rule-based highlighting, prettification, and plugin support. Define any custom language syntax using JSON configuration.
A lightweight, customizable React syntax editor with rule-based highlighting, prettification, and plugin support. Define any custom language syntax using JSON configuration.
- 🎨 Rule-Based Syntax Highlighting - Define highlighting rules via JSON configuration
- 🔧 Fully Customizable - Control styles, colors, classNames, and behavior
- 📦 Lightweight - Zero runtime dependencies (React as peer dependency)
- 🔌 Plugin System - Extend functionality with custom plugins
- 💅 Themeable - CSS variables for easy theming
- 📝 TypeScript Support - Full type definitions included
- ✨ Built-in Prettifier - Format queries with proper indentation
- 🎯 OData Config Included - Ready-to-use OData syntax configuration
npm install react-custom-syntax
# or
yarn add react-custom-syntax
# or
pnpm add react-custom-syntaximport { useState } from 'react'
import { CustomSyntaxEditor, ConfigManager, odataConfig } from 'react-custom-syntax'
function App() {
const [value, setValue] = useState('')
const configManager = new ConfigManager(odataConfig)
const handleChange = e => {
setValue(e.target.value)
}
return (
<CustomSyntaxEditor
value={value}
onChange={handleChange}
configManager={configManager}
headerLabel="OData Query Editor"
/>
)
}Note: Styles are automatically included via CSS modules. No separate import required. CSS variables are available for theming customization.
Define your own syntax by creating a JSON configuration:
{
"version": "1.0.0",
"description": "Custom SQL-like syntax",
"highlightingRules": [
{
"name": "Keywords",
"matchType": "keyword",
"caseSensitive": false,
"addWordBoundaries": true,
"tokens": [
{
"keyword": "SELECT",
"type": "SqlKeyword",
"description": "Select statement"
},
{
"keyword": "FROM",
"type": "SqlKeyword",
"description": "From clause"
}
]
},
{
"name": "Strings",
"matchType": "regex",
"pattern": "'([^']*)'",
"replacement": "<span class='rcse-literal'>'$1'</span>",
"type": "StringLiteral"
}
],
"types": [
{
"className": "SqlKeyword",
"highlightColor": "#569cd6",
"parentType": "Keyword",
"fontWeight": "600"
},
{
"className": "StringLiteral",
"highlightColor": "#ce9178",
"parentType": "Literal",
"fontWeight": "normal"
}
]
}Then use it:
import customConfig from './customSyntax.json'
const configManager = new ConfigManager(customConfig)
;<CustomSyntaxEditor value={value} onChange={handleChange} configManager={configManager} />| Prop | Type | Default | Description |
|---|---|---|---|
value |
string |
Required | Current editor value |
onChange |
(event) => void |
Required | Change handler |
configManager |
ConfigManager |
undefined |
Custom configuration manager |
disabled |
boolean |
false |
Disable the editor |
showHeader |
boolean |
true |
Show/hide header |
headerLabel |
string |
'Syntax Editor' |
Header label text |
headerLinks |
HeaderLink[] |
[] |
Documentation links |
showCopyButton |
boolean |
true |
Show copy button |
showPrettifyButton |
boolean |
true |
Show prettify button |
copyButtonLabel |
string |
'Copy' |
Copy button label |
prettifyButtonLabel |
string |
'Prettify' |
Prettify button label |
placeholder |
string |
'Enter your query...' |
Placeholder text |
className |
string |
undefined |
Custom container class |
style |
CSSProperties |
undefined |
Inline styles |
classNames |
CustomClassNames |
{} |
Custom class names |
styles |
CustomStyles |
{} |
Custom inline styles |
onCopy |
(value) => void |
undefined |
Custom copy handler |
onPrettify |
(value) => void |
undefined |
Custom prettify handler |
notificationHandler |
NotificationHandler |
undefined |
Custom notifications |
disableStyleInjection |
boolean |
false |
Disable CSS injection |
import { ConfigManager } from 'react-custom-syntax'
const config = new ConfigManager(syntaxConfig)
// Get keywords by type
config.getOperatorKeywords()
config.getFunctionKeywords()
config.getCustomTokenKeywords()
// Token information
config.getTokenInfo('and')
config.hasKeyword('contains')
// Generate CSS
const css = config.generateCSS()
// Load from URL
const manager = await ConfigManager.fromURL('https://example.com/syntax.json')import { SyntaxHighlighter } from 'react-custom-syntax'
const highlighter = new SyntaxHighlighter(configManager)
const highlighted = highlighter.highlight('status eq true')import { Prettifier } from 'react-custom-syntax'
const prettifier = new Prettifier(configManager)
const formatted = prettifier.prettify('status eq true and priority gt 5')
// Configure max line length
prettifier.setMaxLineLength(100)import { IdentifierExtractor } from 'react-custom-syntax'
const extractor = new IdentifierExtractor(configManager)
const identifiers = extractor.extract('status eq true and priority gt 5')
// Returns: ['status', 'priority']Extend functionality with custom plugins using lifecycle hooks:
import type { Plugin } from 'react-custom-syntax'
// Example: Logging plugin
const loggingPlugin: Plugin = {
name: 'logging',
beforeHighlight: text => {
console.log('Highlighting text:', text)
return text
},
afterHighlight: (highlightedHtml, originalText) => {
console.log('Highlighted to:', highlightedHtml)
return highlightedHtml
},
beforePrettify: text => {
console.log('Prettifying text:', text)
return text
},
afterPrettify: (prettifiedText, originalText) => {
console.log('Prettified to:', prettifiedText)
return prettifiedText
}
}See the Plugins section for more details.
Override these CSS variables to customize the theme:
:root {
--rcse-bg-primary: #1e1e1e;
--rcse-bg-secondary: #2d2d2d;
--rcse-border-color: #404040;
--rcse-text-primary: #d4d4d4;
--rcse-text-secondary: #666;
--rcse-accent-primary: #1976d2;
--rcse-accent-hover: #264f78;
--rcse-font-mono: 'Consolas', 'Monaco', 'Courier New', monospace;
}<CustomSyntaxEditor
value={value}
onChange={handleChange}
classNames={{
container: 'my-container',
header: 'my-header',
editor: 'my-editor'
}}
/><CustomSyntaxEditor
value={value}
onChange={handleChange}
styles={{
container: { borderRadius: '12px' },
editor: { fontSize: '16px' }
}}
/>import { toast } from 'react-toastify'
const notificationHandler = {
success: msg => toast.success(msg),
warning: msg => toast.warning(msg),
error: msg => toast.error(msg)
}
;<CustomSyntaxEditor value={value} onChange={handleChange} notificationHandler={notificationHandler} /><CustomSyntaxEditor
value={value}
onChange={handleChange}
onCopy={value => {
navigator.clipboard.writeText(value)
console.log('Copied:', value)
}}
onPrettify={prettified => {
console.log('Prettified:', prettified)
}}
/><CustomSyntaxEditor
value={value}
onChange={handleChange}
headerLinks={[
{
href: 'https://docs.example.com',
label: 'Documentation',
title: 'View documentation'
}
]}
/>Two types of matching are supported:
{
"name": "Operators",
"matchType": "keyword",
"caseSensitive": false,
"addWordBoundaries": true,
"tokens": [{ "keyword": "and", "type": "LogicalOperator", "description": "AND operator" }]
}{
"name": "Strings",
"matchType": "regex",
"pattern": "'([^']*)'",
"replacement": "<span class='rcse-literal'>'$1'</span>",
"type": "StringLiteral"
}{
"className": "LogicalOperator",
"highlightColor": "#569cd6",
"parentType": "Operator",
"fontWeight": "600"
}Check out the examples directory for a showcase app with multiple examples:
# Build the main package
npm run build
# Navigate to examples
cd docs
# Install dependencies
npm install
# Run the showcase
npm run devThe showcase includes:
- Basic OData Editor - Simple setup with default OData syntax
- Empty Config - Plain text editor without highlighting
- Custom Simple Syntax - Custom language with 2 keywords
import { useState } from 'react'
import { CustomSyntaxEditor, ConfigManager, odataConfig } from 'react-custom-syntax'
function ODataEditor() {
const [query, setQuery] = useState("status eq 'Active' and priority gt 5")
const configManager = new ConfigManager(odataConfig)
return (
<CustomSyntaxEditor
value={query}
onChange={e => setQuery(e.target.value)}
configManager={configManager}
headerLabel="OData Query Editor"
headerLinks={[
{
href: 'https://azuresearch.github.io/odata-syntax-diagram',
label: 'OData Syntax Reference'
}
]}
/>
)
}<CustomSyntaxEditor value={savedQuery} onChange={() => {}} disabled={true} configManager={configManager} /><CustomSyntaxEditor value={value} onChange={handleChange} showHeader={false} configManager={configManager} />- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
This library uses client-side features (contentEditable, clipboard API). All components are marked with 'use client'
directive and will work seamlessly in Next.js App Router.
// In your Next.js app router component
'use client'
import { CustomSyntaxEditor, ConfigManager, odataConfig } from 'react-custom-syntax'
export default function Page() {
const configManager = new ConfigManager(odataConfig)
// ... rest of your code
}npm run test # Run tests once
npm run test:watch # Run tests in watch mode
npm run test:coverage # Run tests with coverage reportCoverage reports are generated in:
- Terminal: Text summary
coverage/index.html: Interactive HTML report (open in browser)coverage/coverage-final.json: JSON data
MIT © Mohamed Elbaz
Contributions are welcome! Please feel free to submit a Pull Request.
