@sugar-high/react
v2.4.1
Published
Lightweight React code blocks and editors powered by Sugar High
Readme
@sugar-high/react
Lightweight React code blocks and editors powered by Sugar High.
Install
npm install @sugar-high/react reactEditor
import { Editor } from '@sugar-high/react'
<Editor
lang="typescript"
title="app.tsx"
value="const App = () => <main>Hello</main>"
onChange={console.log}
/>Press Tab or Shift+Tab to indent or outdent the caret or selected lines. Customize the indentation
string and underlying textarea attributes with indent and textareaProps:
<Editor
indent="\t"
textareaProps={{ 'aria-label': 'Source code', autoCapitalize: 'off' }}
value={source}
onChange={setSource}
/>FileTree
Compose file navigation with Editor or Code. The parent owns the files and selected path;
the tree owns folder expansion and keyboard focus.
import { useState } from 'react'
import { Editor, FileTree } from '@sugar-high/react'
export function FilesExample() {
const [files, setFiles] = useState<Record<string, string>>({
'src/index.ts': 'export const greeting = "Hello"',
'src/styles.css': 'body { margin: 0; }',
})
const [activeFile, setActiveFile] = useState('src/index.ts')
return (
<div style={{ display: 'grid', gridTemplateColumns: '180px minmax(0, 1fr)' }}>
<FileTree paths={Object.keys(files)} activeFile={activeFile} onActiveFileChange={setActiveFile} />
<Editor title={activeFile} value={files[activeFile]}
onChange={code => setFiles(current => ({ ...current, [activeFile]: code }))} />
</div>
)
}Replace Editor with <Code title={activeFile}>{files[activeFile]}</Code> for read-only viewing.
Pass the same theme to the tree and document for matching colors. Layout is ordinary CSS.
paths, activeFile (string | null), and onActiveFileChange(path) are required.
Paths are relative, slash-separated file names; folders are inferred and initially expanded.
Duplicates are removed. Empty paths, leading/trailing slashes, empty segments, and . / ..
segments are ignored. Callbacks preserve the supplied file path. Folders sort before files.
If files are removed, the parent should update its selection; the tree does not select another file automatically.
Arrow keys navigate and expand/collapse folders; Home/End move to the first/last visible item.
Enter or Space selects a file or toggles a folder. Typing a name moves focus to a matching item.
FileTree accepts standard div attributes, an accessible label (default “Files”), and theme.
Style it through data-sh-file-tree and the existing --sh-* theme variables.
Document cursor, scroll, and undo history are not managed by the tree.
Code
import { Code } from '@sugar-high/react'
<Code lang="python" title="main.py" lineNumbers cx={{ keyword: 'font-bold' }}>
{'def hello():\n return "world"'}
</Code>For excerpts from larger files, set the first displayed line number. Long lines wrap by default;
disable wrapping to use horizontal scrolling instead. Both options also work with Editor.
At the same font and width, Code and Editor use the same wrapping rules. Empty lines retain
a full line height. fontSize applies to source text and filename headers in both components.
Set editor typography once on the root; the textarea and highlighted code inherit it together:
<Editor value={code} onChange={setCode} style={{ fontSize: 14, lineHeight: 1.6 }} />You can also set these styles through className, or use the existing fontSize and
fontFamily props. The default editor line height is 1.5.
The browser layout suite runs with pnpm --filter @sugar-high/react test:browser after
building the package. It uses Node's test runner and an installed agent-browser CLI,
without a site server. It checks typography, blank lines, wrapping, horizontal overflow,
and highlighted character positions against a plain-text mirror of the textarea at
multiple widths. CI installs a pinned CLI and runs the suite in a separate job on
pushes to main, including merged PRs. PR checks skip this job; locally the suite
skips if the CLI is absent.
<Code lineNumbers startingLineNumber={40} wrapLongLines={false}>
{source}
</Code>Experimental WebGPU highlighting
Import the opt-in client components from @sugar-high/react/gpu for large code blocks or editors.
They use gpu-lexer asynchronously and do not load the GPU model from the default React entry.
npm install @sugar-high/react react gpu-lexer'use client'
import { Code, Editor } from '@sugar-high/react/gpu'
<Code lineNumbers>{largeSource}</Code>
<Editor value={source} onChange={setSource} />The components render synchronized plain text while the GPU initializes. Their nested code view
sets data-sh-gpu to pending, ready, or unavailable; when WebGPU is unavailable, the plain
text remains readable and editable. GPU highlighting is language-agnostic, so lang and
extension are accepted only for compatibility and do not affect inference. All other Code and
Editor layout, theme, line-number, and display-hook props continue to work.
lang takes a canonical Sugar High language name. When omitted, title or the legacy
extension prop is resolved through Sugar High's language aliases.
Select languages and render on the server
Use @sugar-high/react/core when you only need a few languages or want a server-compatible static
code block. Import each language configuration explicitly and pass it through lang:
import { Code } from '@sugar-high/react/core'
import * as python from 'sugar-high/lang/python'
<Code lang={python} title="main.py" lineNumbers>
{'def hello():\n return "world"'}
</Code>The core React entry does not include the complete language registry or a client directive. The default entry remains the convenient choice when string language names and title-based detection are more important than selecting the smallest bundle.
Headless highlighting
Highlight keeps parsing and generated token properties while giving you complete control over
the markup. It is server-compatible and accepts the same selective language imports, cx, mark,
and markLine options as core Code.
import { Highlight } from '@sugar-high/react/core'
import * as typescript from 'sugar-high/lang/typescript'
<Highlight
code={source}
lang={typescript}
markLine={(line) => {
if (line.annotations.includes('focus')) line.properties['data-focus'] = true
}}
render={({ lines }) => (
<pre>
{lines.map((line, index) => (
<div key={index} {...line.properties}>
{line.tokens.map((token, tokenIndex) => (
<span key={tokenIndex} {...token.properties}>
{token.value}
</span>
))}
</div>
))}
</pre>
)}
/>The result is not limited to code-block markup. For example, render each generated line as a table row for a diff or virtualized viewer:
<Highlight
code={source}
lang={typescript}
render={({ lines }) => (
<table>
<tbody>
{lines.map((line, index) => (
<tr key={index}>
<th>{index + 1}</th>
<td>{line.tokens.map((token) => token.value).join('')}</td>
</tr>
))}
</tbody>
</table>
)}
/>Use the data-sh-* attributes and --sh-* variables for new styles. The package temporarily
preserves Codice's existing data-codice-* attributes so existing structural selectors can
migrate incrementally.
Themes
Pass a JavaScript theme to Code or Editor. Paired themes follow the inherited CSS
color-scheme, so system and
application theme changes do not require a React rerender:
import { Editor } from '@sugar-high/react'
import { taffy } from '@sugar-high/react/themes'
<Editor theme={taffy} value={source} onChange={setSource} />Enable automatic light and dark selection at the application root. Force a scheme by setting
color-scheme to only light or dark on any ancestor:
:root {
color-scheme: light dark;
}
:root[data-theme='light'] {
color-scheme: light;
}
:root[data-theme='dark'] {
color-scheme: dark;
}Available themes are taffy, vercel, vscode, oneDarkPro, monokai, minimal, gruvbox,
tokyoNight, nordLight, and softMinimal. The last two are light-only palettes; the others
provide light and dark colors. vercel is adapted from the official
Geist Code Block palette.
A single palette stays the same in light and dark color schemes. Custom themes use the same token
names as Sugar High. background and foreground are required; tokens that are not set inherit
foreground:
import type { Theme } from '@sugar-high/react'
const ocean = {
background: '#0f172a',
foreground: '#e2e8f0',
keyword: '#f472b6',
string: '#86efac',
comment: '#64748b',
property: '#7dd3fc',
} satisfies Theme
<Code theme={ocean}>{source}</Code>Use { light, dark } only when the theme should adapt to the inherited color scheme. Optional
component colors are caret, title, control, lineNumber, and lineHighlight.
Low-level styling
Set Sugar High variables on the component itself for a self-contained theme:
const style = {
backgroundColor: '#f6f8fa',
'--sh-editor-background-color': 'transparent',
'--sh-caret-color': '#24292f',
'--sh-title-color': '#57606a',
'--sh-control-color': '#afb8c1',
'--sh-line-number-color': '#8c959f',
'--sh-line-highlight-color': '#fff8c5',
'--sh-keyword': '#cf222e',
'--sh-string': '#0a3069',
} as React.CSSProperties
<Editor style={style} value={source} onChange={setSource} />The --sh-* variables control both the component frame and syntax tokens; see the
theme guide.
| Variable | Applies to | Default | Purpose |
| --- | --- | --- | --- |
| --sh-editor-text-color | Editor | transparent | Textarea text color; normally transparent over highlighted code. |
| --sh-editor-background-color | Editor | transparent | Textarea background color. |
| --sh-caret-color | Both | CanvasText | Editor caret and editable title caret. |
| --sh-font-family | Both | Editor: Consolas, Monaco, monospace; Code: inherited | Code, textarea, and title font family. |
| --sh-font-size | Both | inherit | Code and textarea font size. |
| --sh-padding | Both | 1rem | Shared content and header spacing. |
| --sh-line-number-width | Both | 2.5rem, expanding for 4+ digits | Line-number gutter width. Prefer lineNumbersWidth for an explicit override. |
| --sh-control-color | Both | unset | Header control-dot color. |
| --sh-title-color | Both | unset | Header filename color. |
| --sh-line-number-color | Both | unset | Line-number color. |
| --sh-line-highlight-color | Both | unset | Background for lines selected by highlightLines. |
The editor is a textarea layered over highlighted code. Keep --sh-editor-text-color and
--sh-editor-background-color transparent unless deliberately changing that overlay; set the root's
ordinary color and backgroundColor for the visible surface.
New styles should select component structure through the data-sh-* attributes. Component roots
retain data-codice, data-codice-code, or data-codice-editor compatibility attributes during
the Codice migration. Both components also accept standard div attributes.
