npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@particle-academy/fancy-code

v0.4.2

Published

Lightweight embedded code editor with syntax highlighting and extensible language/theme registries

Downloads

998

Readme

@particle-academy/fancy-code

Lightweight embedded code editor with syntax highlighting, custom toolbar buttons, and extensible language/theme registries. Part of the @particle-academy component ecosystem.

Installation

# npm
npm install @particle-academy/fancy-code

# pnpm
pnpm add @particle-academy/fancy-code

# yarn
yarn add @particle-academy/fancy-code

Peer dependencies: react >= 18, react-dom >= 18, @particle-academy/react-fancy >= 1.5

Usage

Add the @source directive to your main CSS file so Tailwind v4 scans the component library:

@import "tailwindcss";
@import "@particle-academy/fancy-code/styles.css";
@source "../node_modules/@particle-academy/fancy-code/dist/**/*.js";

Then import and use:

import { CodeEditor } from "@particle-academy/fancy-code";
import "@particle-academy/fancy-code/styles.css";

function App() {
  const [code, setCode] = useState('console.log("Hello");');

  return (
    <CodeEditor
      value={code}
      onChange={setCode}
      language="javascript"
      className="rounded-xl border border-zinc-200 dark:border-zinc-700"
    >
      <CodeEditor.Toolbar />
      <CodeEditor.Panel />
      <CodeEditor.StatusBar />
    </CodeEditor>
  );
}

The component has no default border or rounding — add your own via className for standalone use, or embed directly into IDE layouts without visual conflicts.

Commands

pnpm --filter @particle-academy/fancy-code build    # Build with tsup (ESM + CJS + DTS)
pnpm --filter @particle-academy/fancy-code dev      # Watch mode
pnpm --filter @particle-academy/fancy-code lint     # Type-check (tsc --noEmit)
pnpm --filter @particle-academy/fancy-code clean    # Remove dist/

Component API

Compound Components

| Component | Description | |-----------|-------------| | CodeEditor | Root wrapper — context provider, state management | | CodeEditor.Toolbar | Action bar with default buttons or custom children | | CodeEditor.Toolbar.Separator | Vertical divider between toolbar groups | | CodeEditor.Panel | Code editing surface | | CodeEditor.StatusBar | Cursor position, language, tab size display |

CodeEditor Props

interface CodeEditorProps {
  children: ReactNode;
  className?: string;
  value?: string;                  // Controlled value
  defaultValue?: string;           // Uncontrolled initial value
  onChange?: (value: string) => void;
  language?: string;               // Language name or alias (default: "javascript")
  onLanguageChange?: (lang: string) => void;
  theme?: string;                  // "light" | "dark" | "auto" | custom name (default: "auto")
  readOnly?: boolean;              // Default: false
  lineNumbers?: boolean;           // Default: true
  wordWrap?: boolean;              // Default: false
  tabSize?: number;                // Default: 2
  placeholder?: string;
  minHeight?: number;              // Minimum height in px
  maxHeight?: number;              // Max height before scrolling
}

useCodeEditor Hook

Access the editor context from custom toolbar buttons:

import { useCodeEditor } from "@particle-academy/fancy-code";
import { Action } from "@particle-academy/react-fancy";

function RunButton() {
  const { getValue } = useCodeEditor();
  return <Action size="xs" onClick={() => run(getValue())}>Run</Action>;
}

<CodeEditor value={code} onChange={setCode} language="javascript">
  <CodeEditor.Toolbar>
    <RunButton />
    <CodeEditor.Toolbar.Separator />
  </CodeEditor.Toolbar>
  <CodeEditor.Panel />
</CodeEditor>

Context value:

| Method / Property | Description | |-------------------|-------------| | getValue() | Get current document text | | getSelection() | Get currently selected text | | setValue(text) | Replace entire document | | replaceSelection(text) | Replace current selection | | focus() | Focus the editor | | language | Current language name | | setLanguage(name) | Switch active language | | theme | Current theme name | | readOnly | Whether the editor is read-only | | lineNumbers | Whether line numbers are shown | | wordWrap | Whether word wrap is enabled | | tabSize | Current tab size | | toggleWordWrap() | Toggle word wrap on/off | | toggleLineNumbers() | Toggle line numbers on/off | | copyToClipboard() | Copy entire document to clipboard | | cursorPosition | { line, col } — current cursor position | | selectionLength | Length of current selection (0 if none) |

Examples

Basic Editor

<CodeEditor value={code} onChange={setCode} language="typescript">
  <CodeEditor.Toolbar />
  <CodeEditor.Panel />
  <CodeEditor.StatusBar />
</CodeEditor>

Read-Only Code Display

<CodeEditor value={snippet} language="php" readOnly>
  <CodeEditor.Panel />
</CodeEditor>

Custom Toolbar

<CodeEditor value={code} onChange={setCode} language="javascript">
  <CodeEditor.Toolbar>
    <RunButton />
    <CodeEditor.Toolbar.Separator />
    <FormatButton />
    <CopyButton />
  </CodeEditor.Toolbar>
  <CodeEditor.Panel />
  <CodeEditor.StatusBar />
</CodeEditor>

Height Constraints

<CodeEditor value={longCode} language="javascript" maxHeight={400} minHeight={200}>
  <CodeEditor.Toolbar />
  <CodeEditor.Panel />
  <CodeEditor.StatusBar />
</CodeEditor>

Minimal (No Toolbar, No StatusBar)

<CodeEditor value={snippet} language="javascript" readOnly lineNumbers={false}>
  <CodeEditor.Panel />
</CodeEditor>

Built-in Languages

| Language | Aliases | |----------|---------| | JavaScript | js, javascript, jsx | | TypeScript | ts, typescript, tsx | | HTML | html, htm | | PHP | php | | Python | py, python | | Go | go, golang |

Custom Language Registration

Add languages beyond the built-ins using registerLanguage:

import { registerLanguage } from "@particle-academy/fancy-code";
import type { Tokenizer } from "@particle-academy/fancy-code";

const tokenizeRuby: Tokenizer = (source) => {
  const tokens = [];
  // Your regex-based tokenizer logic here
  // See src/engine/tokenizers/javascript.ts for a full example
  return tokens;
};

registerLanguage({
  name: "Ruby",
  aliases: ["rb", "ruby"],
  tokenize: tokenizeRuby,
});

Then use it:

<CodeEditor language="python" ... />

Built-in Color Schemes

| Theme | Description | |-------|-------------| | light | White background, blue/purple/green token colors | | dark | Zinc-900 background, pastel token colors | | auto | Automatically selects light/dark based on prefers-color-scheme (default) |

Custom Theme Registration

Create custom syntax highlighting color schemes using registerTheme:

import { registerTheme } from "@particle-academy/fancy-code";

registerTheme({
  name: "monokai",
  variant: "dark",
  editorTheme: /* editor chrome styles (background, gutter, cursor, selection) */,
  highlightStyle: /* token color definitions (keywords, strings, comments, etc.) */,
});

Then use it:

<CodeEditor theme="monokai" ... />

Theme definition:

interface ThemeDefinition {
  name: string;                    // Unique theme name
  variant: "light" | "dark";      // Light or dark base
  editorTheme: Extension;         // Editor chrome styles (gutter, cursor, selection)
  highlightStyle: HighlightStyle; // Syntax token color definitions
}

Built-in Features

  • Syntax highlighting with language-aware tokenization
  • Line numbers with active line gutter highlight
  • Active line highlighting
  • Bracket matching and auto-close
  • Code folding with fold gutter
  • Search and replace (Ctrl+F / Cmd+F)
  • Autocompletion
  • History (undo/redo)
  • Indent on input
  • Selection highlighting
  • Tab key indentation

All features are reconfigurable at runtime — switching languages, themes, line numbers, word wrap, tab size, and read-only mode happens without recreating the editor.

Customization

All components render data-fancy-code-* attributes on their root elements for external CSS targeting:

| Attribute | Element | |-----------|---------| | data-fancy-code-editor | Root wrapper | | data-fancy-code-toolbar | Toolbar bar | | data-fancy-code-toolbar-separator | Toolbar separator | | data-fancy-code-panel | Editing surface | | data-fancy-code-statusbar | Status bar |

Architecture

Directory Layout

src/
├── components/
│   └── CodeEditor/
│       ├── CodeEditor.tsx              # Root compound component + context
│       ├── CodeEditor.types.ts         # All prop/context types
│       ├── CodeEditor.context.ts       # React context + useCodeEditor hook
│       ├── CodeEditorPanel.tsx         # Editing surface
│       ├── CodeEditorToolbar.tsx       # Default toolbar + custom children
│       ├── CodeEditorToolbarSeparator.tsx
│       ├── CodeEditorStatusBar.tsx     # Cursor/language/tab display
│       └── index.ts
├── hooks/
│   ├── use-editor-engine.ts            # Core editor lifecycle
│   └── use-dark-mode.ts               # Reactive prefers-color-scheme
├── languages/
│   ├── registry.ts                     # Global language registry
│   ├── builtin.ts                     # JS, TS, HTML, PHP registrations
│   ├── types.ts                       # LanguageDefinition type
│   └── index.ts
├── themes/
│   ├── registry.ts                     # Global theme registry
│   ├── light.ts                       # Built-in light color scheme
│   ├── dark.ts                        # Built-in dark color scheme
│   ├── types.ts                       # ThemeDefinition type
│   └── index.ts
├── styles.css                         # Base structural styles
└── index.ts                           # Public API

Public Exports

// Components
export { CodeEditor, useCodeEditor };
export type { CodeEditorProps, CodeEditorContextValue, CodeEditorToolbarProps, CodeEditorPanelProps, CodeEditorStatusBarProps };

// Language registration
export { registerLanguage, getLanguage, getRegisteredLanguages };
export type { LanguageDefinition };

// Theme registration
export { registerTheme, getTheme, getRegisteredThemes };
export type { ThemeDefinition };

Related: TreeNav from react-fancy

For IDE-style layouts, pair CodeEditor with the TreeNav component from @particle-academy/react-fancy. TreeNav provides hierarchical file/folder navigation with expand/collapse, selection, and extension-based file icons.

import { TreeNav } from "@particle-academy/react-fancy";
import { CodeEditor } from "@particle-academy/fancy-code";

<div className="flex" style={{ height: 600 }}>
  <TreeNav
    nodes={fileTree}
    selectedId={activeFile}
    onSelect={(id, node) => openFile(id, node)}
  />
  <CodeEditor value={code} onChange={setCode} language={lang}>
    <CodeEditor.Toolbar />
    <CodeEditor.Panel />
    <CodeEditor.StatusBar />
  </CodeEditor>
</div>

See the full IDE demo at /react-demos/ide in the monorepo.

Demo Pages

The demo page lives in the monorepo at resources/js/react-demos/pages/CodeEditorDemo.tsx and is accessible at /react-demos/code-editor. It demonstrates:

  1. Basic editor with default toolbar
  2. Read-only code display
  3. Custom toolbar buttons via useCodeEditor()
  4. Language switching (JS, TS, HTML, PHP)
  5. Light vs dark theme showcase
  6. Word wrap and configuration
  7. Minimal mode (no toolbar, no status bar)

Agent Guidelines

Guidelines for AI agents (Claude Code, Copilot, etc.) working on this package.

Component Pattern

  • Uses the same compound component pattern as @particle-academy/react-fancy's Editor: Object.assign(Root, { Toolbar, Panel, StatusBar }).
  • Context is provided via CodeEditorContext and consumed with useCodeEditor().
  • All root elements have data-fancy-code-* attributes for external CSS targeting.

Editor Engine

  • The useCodemirror hook manages the editor lifecycle.
  • Uses compartments for hot-swapping language, theme, line numbers, word wrap, tab size, read-only, placeholder, and height constraints.
  • External value changes are synced via isExternalUpdate ref to prevent onChange loops.
  • SSR-safe: the editor is only created inside useEffect.

Extension System

  • Languages and themes use the same global registry pattern as react-fancy's registerExtension.
  • Built-in languages/themes are registered via side-effect imports in languages/builtin.ts and themes/light.ts/themes/dark.ts.
  • The sideEffects field in package.json lists these files to prevent tree-shaking.

Build

  • tsup handles the build — ESM, CJS, and .d.ts generation.
  • react, react-dom, and @particle-academy/react-fancy are external dependencies.
  • All editor engine packages are bundled (not external) so consumers don't need to install them.
  • After any change, verify with npm run build from the monorepo root.