Componenteditors1.26 KB gzipZero dependencies
Json Editor
JSON editor with live validation, error line and format button.
Preview
Live Preview
JSON
✓ Valid
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add json-editorOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { JsonEditor } from "@/components/ui/json-editor";
<JsonEditor />Usage guidance
Use Json Editor when a ticket stub surface needs JSON editor with live validation, error line and format button. Keep props minimal and prefer composition with sibling stubs.
Avoid Json Editor for long form flows or dense data grids where a dedicated layout block fits better.
Source code
json-editor.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface JsonEditorProps extends React.HTMLAttributes<HTMLDivElement> {
value?: string;
defaultValue?: string;
onValueChange?: (value: string, valid: boolean) => void;
height?: string;
}
function format(src: string): string {
try { return JSON.stringify(JSON.parse(src), null, 2); } catch { return src; }
}
function validate(src: string): { valid: boolean; error?: string } {
if (src.trim() === "") return { valid: true };
try { JSON.parse(src); return { valid: true }; } catch (e) { return { valid: false, error: (e as Error).message }; }
}
/** Indented JSON editor with live syntax validation and format action. */
export function JsonEditor({ value, defaultValue = '{\n "name": "bigbullui",\n "version": "0.1.0"\n}', onValueChange, height = "200px", className, ...props }: JsonEditorProps) {
const [internal, setInternal] = React.useState(defaultValue);
const src = value ?? internal;
const { valid, error } = validate(src);
const handleChange = (v: string) => {
setInternal(v);
const check = validate(v);
onValueChange?.(v, check.valid);
};
return (
<div className={cn("w-full overflow-hidden rounded-lg border bg-card transition-colors duration-200 motion-reduce:transition-none", valid ? "border-border" : "border-destructive", className)} {...props}>
<style>{`@keyframes jeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }`}</style>
<div className="flex items-center justify-between border-b border-border bg-secondary/60 px-3 py-1.5">
<span className="font-mono text-[10px] uppercase tracking-wider text-muted-foreground">JSON</span>
<div className="flex items-center gap-2">
{valid ? (
<span className="font-mono text-[9px] uppercase tracking-wider text-success">✓ Valid</span>
) : (
<span className="font-mono text-[9px] uppercase tracking-wider text-destructive animate-[jeIn_0.2s_ease-out] motion-reduce:animate-none">✗ Invalid</span>
)}
<button
onClick={() => handleChange(format(src))}
disabled={!valid}
className="rounded-sm border border-border px-2 py-0.5 font-mono text-[9px] uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground disabled:opacity-40 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring motion-reduce:transition-none"
>
Format
</button>
</div>
</div>
<textarea
value={src}
onChange={(e) => handleChange(e.target.value)}
style={{ height }}
aria-label="JSON editor"
aria-invalid={!valid}
spellCheck={false}
className="w-full resize-none bg-transparent p-3 font-mono text-xs leading-5 text-foreground focus-visible:outline-none"
/>
{!valid && error && (
<p className="border-t border-destructive/30 bg-destructive/5 px-3 py-1.5 font-mono text-[10px] text-destructive">{error}</p>
)}
</div>
);
}
Props
| Prop | Type | Description |
|---|---|---|
| value | string | |
| defaultValue | string | |
| onValueChange | (value: string, valid: boolean) => void | |
| height | string |