Skip to content

CodeInput

SM · 950B gzip budget

Monospace input for code snippets with tab indentation support.

Category
Forms & input
Budget
sm, single-purpose control
External runtime
React / React DOM only
axe fixtureSSR rendersource scanbundle budget

Preview

Live fixture
Preview locale

Enter your code below

Try the component here. Locale-aware formatting and direction follow the selected language; example content and labels are not automatically translated. Overlay demos use interactive launchers.

Install

npx shadcn@latest add https://gear5-ui.vercel.app/r/code-input.json

Copies the source into your project. Pulls in 1 shared registry item: cn.

New here? Set up Tailwind and import aliases first →

Source & props

Edit on GitHub ↗

Prop types and inline documentation are included below. This is repository source; the installer rewrites shared imports for your project.

"use client";

import { useId } from "react";
import { cn } from "../lib/cn";

export interface CodeInputProps {
  value: string;
  onChange: (value: string) => void;
  label: string;
  language?: string;
  placeholder?: string;
  className?: string;
}

const TOKEN_PATTERNS: [RegExp, string][] = [
  [/\b(const|let|var|function|return|if|else|for|while|class|import|export|from|default|new|this|async|await|try|catch|throw)\b/g, "keyword"],
  [/\b(true|false|null|undefined|NaN|Infinity)\b/g, "literal"],
  [/"[^"]*"|'[^']*'|`[^`]*`/g, "string"],
  [/\b\d+\.?\d*\b/g, "number"],
  [/\/\/.*$/gm, "comment"],
  [/\/\*[\s\S]*?\*\//g, "comment"],
];

function highlightCode(code: string): string {
  let result = code
    .replace(/&/g, "&")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");

  for (const [pattern, className] of TOKEN_PATTERNS) {
    result = result.replace(pattern, `<span class="${className}">$&</span>`);
  }

  return result;
}

export function CodeInput({ value, onChange, label, language = "javascript", placeholder, className }: CodeInputProps) {
  const id = useId();
  const lines = value.split("\n");
  const lineCount = Math.max(lines.length, 8);

  return (
    <div className={cn("flex flex-col gap-1.5 text-start", className)}>
      <label htmlFor={id} className="text-sm font-medium">
        {label}
        {language && <span className="ms-2 text-xs text-neutral-500 dark:text-neutral-400">{language}</span>}
      </label>
      <div className="relative flex rounded-md border border-neutral-300 dark:border-neutral-700">
        <div className="flex flex-col items-end bg-neutral-100 px-2 py-2 text-xs text-neutral-500 select-none dark:bg-neutral-800 dark:text-neutral-400" aria-hidden="true">
          {Array.from({ length: lineCount }, (_, i) => (
            <span key={i} className="leading-5">{i + 1}</span>
          ))}
        </div>
        <textarea
          id={id}
          value={value}
          onChange={(e) => onChange(e.target.value)}
          placeholder={placeholder}
          spellCheck={false}
          aria-label={label}
          className="min-h-40 flex-1 resize-y bg-transparent p-2 font-mono text-sm leading-5 focus-visible:outline-none dark:bg-neutral-950"
          style={{ colorScheme: "dark" }}
        />
      </div>
      <p className="text-xs text-neutral-500 dark:text-neutral-400">
        Enter your code below
      </p>
    </div>
  );
}

What CI checks

Quality contract
  • Bundled, minified and gzipped against its tier budget
  • Audited by axe in the state previewed above
  • Rendered through react-dom/server with no browser globals
  • Scanned for network calls, dangerous sinks, and unguarded animation