Stats

How each model writes UI.

At a glance

  • 58%of all gray classes are Tailwind's slate
  • 14 of 15models pick indigo or blue as their accent
  • 12 of 15models cast someone named Maya Chen

Findings

The most interesting things in the code, from one model's bug to habits all 15 share.

  1. Haiku 4.5Crashes its file browser on mount: a useCallback lists itself in its own dependency array.
    const handleFileInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    // …
    }, [currentFolderId, addToast, handleFileInput]);
    File browser
  2. GPT-5.6 LunaLeaves the delay off its playback timer, so the interval fires every few milliseconds and a three-minute song plays out in about a second.
    timer.current = window.setInterval(() => setPosition(p => {
    // …
      setCurrentId(nextId || tracks[0].id); return 0;
    }));
    Music player
  3. 15 of 15 music playersKeep time by adding a second on every timer tick. None check the clock, so playback drifts whenever a tick runs late.
    const id = window.setInterval(() => setPosition((p) => p + 1), 1000);
    Fable 5.1 · Music player
  4. Composer 2.5Freezes the page on load: in its formula expander, the branches for cell references and ranges never advance the loop index.
    } else if (isCellRef(t)) {
      if (!isValidAddr(t)) throw new Error('#REF!');
      const r = evalCell(sheetId, t.toUpperCase(), ctx);
    // …
    } else {
      out.push(t);
      i++;
    }
    Spreadsheet
  5. 4 of 15 spreadsheetsEvaluate formulas by handing strings to the Function constructor. Ten write a recursive-descent parser, and GLM 5.2 a Pratt parser.
    const n = Function(`"use strict"; return (${exp})`)();
    GPT-5.6 Terra · Spreadsheet
  6. 15 of 15 spreadsheetsCatch circular references, and spell them four ways: #CIRC!, #CYCLE!, #CIRCULAR!, or a plain #REF!.
  7. Gemini 3.1 ProRenders email bodies as raw HTML, the classic cross-site scripting hole in a mail client.
    dangerouslySetInnerHTML={{ __html: msg.body }}
    Email client
  8. 11 of 15 modelsWrite their own seeded random numbers to stay deterministic. Sonnet 5 borrows a noise one-liner from GLSL shaders; the four OpenAI models shape their data by hand.
    const x = Math.sin(seed * 12.9898 + 78.233) * 43758.5453;
    Sonnet 5 · Analytics dashboard
  9. Gemini 3.8 FlashPromises “no Math.random()” in a comment, then builds toast IDs from Date.now() and Math.random() in the same file.
    // Helper: Deterministic pseudo-random shuffle (no Math.random())
    // …
    const id = `toast-${Date.now().toString()}-${Math.random().toString().slice(2, 6)}`;
    Music player
  10. All 180 appsKeep their state in useState. Only Opus 5.5's music player adds a useReducer, and 8 apps from Fable 5.1, Opus 5.5 and Sonnet 5 pass values down through React context.
    const [player, dispatch] = useReducer(playerReducer, undefined, createInitialPlayer);
    Opus 5.5 · Music player
  11. 3 of 3 GPT-5.6 modelsNever write an interface: 171 type aliases, while Codex 5.3, also from OpenAI, writes 59 interfaces.
    type Attachment = { name: string; size: string; kind: "pdf" | "image" | "sheet" };
    type ThreadMessage = { id: string; from: string; email: string; time: string; body: string; mine?: boolean };
    GPT-5.6 Terra · Email client

What day is it?

The prompt asks for a fixed “now”, and most apps use September 23, 2026, the day of the run. Opus 5.5 ran three days later, on September 26, and dates its apps between September 18 and 26, except for a calendar set in March. Haiku 4.5 sets 7 of its 12 apps in 2024.

Hand-typed timestamps that miss the day

  • Kimi K2.7 CodeFile browser
    REFERENCE_MS = 1761328800000; // 2026-09-23T18:00:00Z
    Decodes to October 24, 2025
  • Gemini 3.8 FlashForum
    FIXED_NOW = 1774310400000; // Wednesday Sep 23, 2026, 12:00:00 UTC
    Decodes to March 24, 2026
  • Gemini 3.1 ProForum
    BASE_TIMESTAMP = 1792756800000; // 2026-09-23T12:00:00.000Z
    Decodes to October 23, 2026
  • GLM 5.2Social feed
    REF_NOW_MS = 1727107980000;
    Decodes to September 23, 2024

Fingerprints

What stands out in each model's 12 apps and 21 components. Gray is its main gray family and that family's share of its grays. Open a trait to see it in the code.

Opus 5.5

Anthropic

Typesets its apps: tabular-nums on figures and · between metadata, more of both than any model.

Gray
slate53%
Accent
indigo
Corners
rounded-md
  • Draws icons from one table of SVG paths through an <Icon name> component, in 8 of its 12 apps; no other model does in more than 3
    const ICONS = {
      inbox: [
        'M22 12h-6l-2 3h-4l-2-3H2',
    // …
    type IconName = keyof typeof ICONS;
    // …
    {ICONS[name].map((d) => (
      <path key={d} d={d} />
    ))}
    Email client
  • Switches off React's exhaustive-deps rule 14 times, twice as often as any other model
    const starredCount = useMemo(() => items.filter((i) => i.starred && isAlive(i)).length, [items]); // eslint-disable-line react-hooks/exhaustive-deps
    File browser

Grok 4.7

xAI

Warm stone grays in light mode that flip to cool zinc in dark mode.

Gray
zinc58%
Accent
teal
Corners
rounded-md
  • Handlers are nested functions, rarely named handleX
    function markRead(id: string) {
      setThreads((current) =>
        current.map((thread) =>
          thread.id === id
            ? { ...thread, messages: thread.messages.map((item) => ({ ...item, unread: false })) }
            : thread,
        ),
      );
    }
    Email client
  • Respects reduced motion in 5 of its 12 apps, more than any model; only two others do, in 4 apps between them
    <div className="h-24 animate-pulse rounded-xl bg-stone-200 motion-reduce:animate-none dark:bg-slate-800" />
    Analytics dashboard

Gemini 3.8 Flash

Google

Offers several names for one prop: leftIcon, leadingIcon, startIcon and iconStart.

Gray
slate73%
Accent
blue
Corners
rounded
  • About 70 JSX comments per app, nearly five times the next model
    {/* Navigation Section */}
    <div className="flex-1 overflow-y-auto px-2 space-y-5 py-1 text-xs">
      {/* Mailboxes Group */}
    Email client
  • Exports every interface, even in a one-file app
    export interface LabelInfo {
      id: LabelId;
      name: string;
      dotColor: string;
      badgeBg: string;
      badgeText: string;
    }
    Email client

Fable 5.1

Anthropic

Builds apps from tiny primitives like IconButton, Kbd and EmptyState around a thin root.

Gray
slate45%
Accent
indigo
Corners
rounded-md
  • JSDoc on the props of all 21 components; six models write none
    /** Visual style of the button. */
    variant?: ButtonVariant;
    /** Size preset. `icon` renders a square button intended for a single icon child. */
    size?: ButtonSize;
    /** Shows a spinner, sets `aria-busy`, and prevents interaction while true. */
    loading?: boolean;
    Button component
  • Switches off React's exhaustive-deps rule 7 times, second only to Opus 5.5
    () => sliceData(records, filters, currentStart, filters.range),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [records, filters.range, filters.channel, filters.device, filters.country],
    Analytics dashboard

GPT-5.6 Sol

OpenAI

Packs whole panels onto single lines, some over 3,600 characters, with no comments.

Gray
slate94%
Accent
indigo
Corners
rounded-lg
  • Snapshots undo history with structuredClone, which no other model uses
    const snapshot = useCallback((): History => ({ sheets: cloneSheets(sheets), sheetId, selection: structuredClone(selection) }), [sheets, sheetId, selection]);
    Spreadsheet
  • focus: rings where others use focus-visible
    className="ml-auto rounded-md p-1.5 text-slate-400 hover:bg-slate-200 hover:text-slate-700 focus:outline-none focus:ring-2 focus:ring-violet-500 dark:hover:bg-slate-800 dark:hover:text-slate-200"
    Email client

Kimi K3

Moonshot AI

Switches gray family from file to file: zinc, neutral, slate or plain gray.

Gray
slate36%
Accent
blue
Corners
rounded-lg
  • Keeps SVG gradient ids unique with useId
    const gid = useId();
    // …
    <linearGradient id={gid} x1="0" y1="0" x2="0" y2="1">
    Analytics dashboard
  • Memoizes spreadsheet cells with React.memo
    const CellView = React.memo(function CellView(props: CellViewProps) {
    Spreadsheet

GPT-5.6 Terra

OpenAI

Comment-free apps in 1,000-character lines, a third without spaces after commas.

Gray
slate100%
Accent
indigo
Corners
rounded-lg
  • Its Icon helper opens exactly like GPT-5.6 Sol's
    const Icon = ({ name, className = "h-4 w-4" }: { name: string; className?: string }) => {
    Analytics dashboard
  • Handlers are bare verbs like update() and open()
    const update = (id:string, change:Partial<Mail>) => setMails(ms=>ms.map(m=>m.id===id?{...m,...change}:m));
    const open = (m: Mail) => { setSelected(m.id); if(m.unread) update(m.id,{unread:false}); };
    Email client

GPT-5.6 Luna

OpenAI

Comment-free lines thousands of characters long, hand-tuned with values like tracking-[.18em].

Gray
slate100%
Accent
indigo
Corners
rounded-lg
  • Reaches for Array.at(-1), which no other model's apps use
    const prev = history.at(-1); if (!prev) return;
    Spreadsheet
  • Unicode glyphs like ◈ and ▶ stand in for icons
    {kind === "image" ? "◈" : kind === "video" ? "▶" : kind === "sheet" ? "XLS" : kind === "archive" ? "ZIP" : kind.toUpperCase()}
    File browser

Sonnet 5

Anthropic

Writes each app like a documented codebase: product banner, section dividers, small components.

Gray
slate52%
Accent
indigo
Corners
rounded-md
  • A named SVG component for every icon
    function PlusIcon(props: React.SVGProps<SVGSVGElement>) {
      return (
        <svg viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" {...props}>
          <path d="M10 4v12M4 10h12" />
        </svg>
      );
    }
    Calendar
  • Splits apps into the most components, down to a one-line divider
    function ToolbarDivider() {
      return <div className="mx-1 h-5 w-px bg-slate-300 dark:bg-slate-600" aria-hidden="true" />;
    }
    Spreadsheet

GLM 5.2

Zhipu

Compressed TypeScript: one-line interfaces and tiny positional factories like mk() and att().

Gray
slate97%
Accent
blue
Corners
rounded-md
  • The only model to size charts with ResizeObserver
    const ro = new ResizeObserver((entries) => {
      for (const e of entries) setWidth(Math.max(320, e.contentRect.width));
    });
    ro.observe(el);
    Analytics dashboard
  • Toggles dark mode on the host page's <html>, outside its own root
    if (dark) document.documentElement.classList.add("dark");
    else document.documentElement.classList.remove("dark");
    Calendar

Kimi K2.7 Code

Moonshot AI

Stiff, nearly contraction-free UI copy: “I am seeing a weird flicker.”

Gray
slate70%
Accent
indigo
Corners
rounded-md
  • Writes custom hooks in 5 of its 12 apps, more than any model
    function useDropdown() {
      const [open, setOpen] = useState(false);
      const ref = useRef<HTMLDivElement>(null);
      useEffect(() => {
        function onClick(e: MouseEvent) {
    Issue tracker
  • Follows the OS theme live with a matchMedia listener
    const m = window.matchMedia('(prefers-color-scheme: dark)');
    setSystemDark(m.matches);
    const handler = (e: MediaQueryListEvent) => setSystemDark(e.matches);
    m.addEventListener('change', handler);
    Settings

Composer 2.5

Cursor

Hedges component APIs with fallback chains like props.startIcon ?? props.leadingIcon ?? props.iconStart.

Gray
zinc51%
Accent
blue
Corners
rounded-lg
  • Box-drawing // ─── section ─── banners
    // ─── Types ──────────────────────────────────────────────────────────────────
    Email client
  • Hard-codes the 1280px frame into its layout math
    const left = Math.min(anchor.x, 1280 - panelW - 16);
    Calendar

Codex 5.3

OpenAI

One giant, comment-free component per app, 570 to 1,150 lines long.

Gray
slate96%
Accent
blue
Corners
rounded-md
  • Memoizes values but never callbacks: no useCallback in any of its 12 apps
    const mailboxStats = useMemo(() => {
      const unreadByMailbox: Record<Mailbox, number> = {
        inbox: 0,
    Email client
  • Verb-first actions like archiveThread, rarely handleX
    function toggleStar(threadId: string) {
      setThreads((prev) => prev.map((thread) => (thread.id === threadId ? { ...thread, starred: !thread.starred } : thread)));
    }
    
    function archiveThread(threadId: string) {
    Email client

Gemini 3.1 Pro

Google

Pastes Feather and Heroicons SVG source verbatim, down to xmlns and closing </line> tags.

Gray
gray62%
Accent
blue
Corners
rounded
  • Writes any 111 times, where five models never do
    Inbox: (props: any) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}>
    Email client
  • shadcn animate-in classes that plain Tailwind ignores
    className="bg-white dark:bg-gray-800 rounded-xl shadow-2xl w-full max-w-md overflow-hidden animate-in fade-in zoom-in-95 duration-200"
    Calendar

Haiku 4.5

Anthropic

Swaps drawn icons for emoji and glyphs: ⏸ ⏭ transport buttons, 📊 empty states.

Gray
gray66%
Accent
blue
Corners
rounded
  • Asks picsum for random photos, so images change on every load
    url: 'https://picsum.photos/600/400?random=1'
    Social feed
  • Math.random() in its mock data, despite the ban
    stats.visitors > 0 ? (Math.random() * 0.12 + 0.06) * 100 : 0,
    Analytics dashboard

By the numbers

Across each model's 12 apps.

App size

Median code per app.

  1. Gemini 3.8 Flash125 KB
  2. Grok 4.7114 KB
  3. Fable 5.1106 KB
  4. Opus 5.5104 KB
  5. Sonnet 596 KB
  6. Kimi K388 KB
  7. GLM 5.271 KB
  8. Kimi K2.7 Code69 KB
  9. Composer 2.565 KB
  10. Codex 5.359 KB
  11. Haiku 4.553 KB
  12. Gemini 3.1 Pro48 KB
  13. GPT-5.6 Sol43 KB
  14. GPT-5.6 Terra30 KB
  15. GPT-5.6 Luna29 KB

Components

Average components per app, not counting icons.

  1. Sonnet 515.3
  2. GLM 5.214.1
  3. Fable 5.113.8
  4. Opus 5.513.0
  5. Kimi K312.2
  6. Grok 4.79.0
  7. Composer 2.59.0
  8. Kimi K2.7 Code8.8
  9. GPT-5.6 Sol6.2
  10. GPT-5.6 Luna6.0
  11. Gemini 3.1 Pro5.8
  12. GPT-5.6 Terra5.3
  13. Haiku 4.53.8
  14. Gemini 3.8 Flash3.7
  15. Codex 5.32.2

ARIA attributes

Average aria-* attributes per app.

  1. Grok 4.759
  2. Opus 5.558
  3. Fable 5.156
  4. Sonnet 542
  5. Kimi K341
  6. Composer 2.529
  7. GPT-5.6 Sol16
  8. GLM 5.215
  9. Kimi K2.7 Code15
  10. Gemini 3.8 Flash13
  11. GPT-5.6 Terra13
  12. GPT-5.6 Luna11
  13. Codex 5.310
  14. Gemini 3.1 Pro1
  15. Haiku 4.51

Line length

Median characters per line.

  1. GPT-5.6 Terra192
  2. GPT-5.6 Luna190
  3. GPT-5.6 Sol91
  4. Gemini 3.1 Pro52
  5. GLM 5.250
  6. Opus 5.547
  7. Composer 2.547
  8. Gemini 3.8 Flash43
  9. Fable 5.143
  10. Grok 4.742
  11. Kimi K342
  12. Sonnet 542
  13. Codex 5.342
  14. Kimi K2.7 Code39
  15. Haiku 4.535

From the latest runs: 180 apps and 315 components. The numbers are pattern counts over the source, so read them as close rather than exact. Signatures, traits, and quirks come from reading each model's code, and each was checked by searching every model's files; a test checks every snippet against the published code. Names leave out the issue tracker, whose prompt mentions a Maya. All of it describes habits, not quality.