Arc 01 / VS Code extension

Code through
the next arc.

MangaMode is a collection of anime and manga-inspired editor themes designed to keep long coding days readable, expressive, and easy on the eyes.

Preview theme

arcs.ts

import { readFile } from 'node:fs/promises';

type Arc = { title: string; chapters: number[] };

/** Load every arc and keep the ones worth binge-reading. */
export async function longArcs(path: string, min = 30): Promise<Arc[]> {
  const arcs: Arc[] = JSON.parse(await readFile(path, 'utf8'));

  return arcs
    .filter(({ chapters }) => chapters.length >= min)
    .map((arc) => ({
      ...arc,
      title: `${arc.title} (${arc.chapters.length} ch.)`
    }));
}
MangaModeEight themes · one extension

The brief

Long sessions.
Less visual noise.

I created MangaMode because I wanted editor themes I could comfortably use throughout a full day of coding. Many colorful themes look exciting in a screenshot but become tiring when every hue is bright, every token demands attention, and the interface competes with the code.

The goal was not to remove personality. It was to control it: pair recognizable anime-inspired palettes with low-glare surfaces, restrained saturation, and syntax contrast that stays useful over time.

I held every theme to three constraints:

  • Recognizable. Each palette has to read as its series at a glance.
  • Readable. Every text color passes WCAG AA, and the high-contrast Death Note passes AAA.
  • Complete. Every surface you see day to day gets a theme color, so VS Code’s default blue and grey never leak in.

Decisions

Four calls
that shaped it.

  • Depth, not rainbow

    Instead of

    A different hue per nesting level, the common default. MangaMode started with six.

    Chose: Three tonal shades of one hue for nested brackets.

    Why: Nesting should read as depth. Six hues added visual noise exactly where code is hardest to follow.

    Evidencev1.1.0 cut brackets from 6 hues to 3 shades in every theme.

  • Contrast is a gate

    Instead of

    Checking colors by eye in a few screenshots.

    Chose: A script that fails the release build when an editor text color drops below 4.5:1, or 7:1 for high contrast.

    Why: Checking by eye missed real failures. Inlay hints used a transparent color that put them under 3:1 in every theme.

    Evidencev1.3.0 moved hints to 5–6:1 and lifted line numbers above 4.5:1 in four themes.

  • Hue, not just lightness

    Instead of

    Telling roles apart by lightness alone, or reusing one accent for everything.

    Chose: Separate code roles by hue.

    Why: Pokémon's functions and types were both neutral grey, and the cursor shared the keyword color in five themes, so different things looked the same.

    Evidencev1.3.0 moved Pokémon functions and types to a soft blue and gave the cursor its own color.

  • Darken, don't re-color

    Instead of

    Swapping in new, stronger hues that pass more easily.

    Chose: Fix the light theme's contrast by darkening each color and keeping its hue.

    Why: The pastel hues are what make it Sailor Moon. New hues would pass the check and lose the theme.

    Evidencev1.3.0 darkened syntax, line numbers, inlay hints and inactive tabs to 4.5:1 on cream, hues unchanged.

Try it

Find the
edge.

Pick a theme and a token, then drag its lightness. The bench moves lightness in OKLCH so the hue holds while you drag. The black band marks where the color stops being readable.

Theme
// Personality, under control.import { defineTheme } from 'mangamode';type Volume = { name: string; issue: number };export function readChapter(vol: Volume, page = 1) {  const panels = vol.issue * 24;  return format(vol.name, [page, panels]);}defineTheme({ glare: 'low', focus: 'high' });
Token

4.7:1

#D95E63 on #0B1B24. Passes AA, the bar this theme ships at.

Black marks lightness below 4.5:1. The red tick is the shipped color.

The theme file

Every key
has a place.

Point at a block of the theme file to see what it paints. The values are read from the published file of the theme you pick, and the editor beside it is drawn from the same file.

Theme file

themes/one-piece-color-theme.json (excerpt)

{"colors": {},"tokenColors": []}
import { readFile } from 'node:fs/promises';

type Arc = { title: string; chapters: number[] };

/** Load every arc and keep the ones worth binge-reading. */
export async function longArcs(path: string, min = 30): Promise<Arc[]> {
  const arcs: Arc[] = JSON.parse(await readFile(path, 'utf8'));

  return arcs
    .filter(({ chapters }) => chapters.length >= min)
    .map((arc) => ({
      ...arc,
      title: `${arc.title} (${arc.chapters.length} ch.)`
    }));
}

Point at a block of the file to see what it paints.

The system

One file,
four places.

Each theme is a single JSON file. The extension, the contrast check, mangamode.dev and the editors on this page all read it directly.

themes/*.json

  • VS Code extensionPackaged on every release.
  • Contrast checkRuns in the release build and blocks the packaged extension if an editor text color falls below its floor.
  • mangamode.devSwatches, contrast ratios, and the code sample.
  • This pageThe editors, the bench, and the live preview.

Theme lineup

Pick your
mode.

Seven dark themes, including a dedicated high-contrast mode, and one light theme translate familiar worlds into practical editor palettes. Each editor below is drawn from the theme file itself, not a screenshot.

import { readFile } from 'node:fs/promises';

type Arc = { title: string; chapters: number[] };

/** Load every arc and keep the ones worth binge-reading. */
export async function longArcs(path: string, min = 30): Promise<Arc[]> {
  const arcs: Arc[] = JSON.parse(await readFile(path, 'utf8'));

  return arcs
    .filter(({ chapters }) => chapters.length >= min)
    .map((arc) => ({
      ...arc,
      title: `${arc.title} (${arc.chapters.length} ch.)`
    }));
}

One Piece

Ocean teals, muted reds, and a deep navy editor canvas.

import { readFile } from 'node:fs/promises';

type Arc = { title: string; chapters: number[] };

/** Load every arc and keep the ones worth binge-reading. */
export async function longArcs(path: string, min = 30): Promise<Arc[]> {
  const arcs: Arc[] = JSON.parse(await readFile(path, 'utf8'));

  return arcs
    .filter(({ chapters }) => chapters.length >= min)
    .map((arc) => ({
      ...arc,
      title: `${arc.title} (${arc.chapters.length} ch.)`
    }));
}

Dragon Ball

Bold oranges and deep blues with the brightness kept in check.

import { readFile } from 'node:fs/promises';

type Arc = { title: string; chapters: number[] };

/** Load every arc and keep the ones worth binge-reading. */
export async function longArcs(path: string, min = 30): Promise<Arc[]> {
  const arcs: Arc[] = JSON.parse(await readFile(path, 'utf8'));

  return arcs
    .filter(({ chapters }) => chapters.length >= min)
    .map((arc) => ({
      ...arc,
      title: `${arc.title} (${arc.chapters.length} ch.)`
    }));
}

Pokémon

Recognizable yellows balanced by soft greys and cool blues.

import { readFile } from 'node:fs/promises';

type Arc = { title: string; chapters: number[] };

/** Load every arc and keep the ones worth binge-reading. */
export async function longArcs(path: string, min = 30): Promise<Arc[]> {
  const arcs: Arc[] = JSON.parse(await readFile(path, 'utf8'));

  return arcs
    .filter(({ chapters }) => chapters.length >= min)
    .map((arc) => ({
      ...arc,
      title: `${arc.title} (${arc.chapters.length} ch.)`
    }));
}

Demon Slayer

Charcoal foundations with water-blue and fire-red accents.

import { readFile } from 'node:fs/promises';

type Arc = { title: string; chapters: number[] };

/** Load every arc and keep the ones worth binge-reading. */
export async function longArcs(path: string, min = 30): Promise<Arc[]> {
  const arcs: Arc[] = JSON.parse(await readFile(path, 'utf8'));

  return arcs
    .filter(({ chapters }) => chapters.length >= min)
    .map((arc) => ({
      ...arc,
      title: `${arc.title} (${arc.chapters.length} ch.)`
    }));
}

Jujutsu Kaisen

Dark violets and restrained cyan inspired by Hollow Purple.

import { readFile } from 'node:fs/promises';

type Arc = { title: string; chapters: number[] };

/** Load every arc and keep the ones worth binge-reading. */
export async function longArcs(path: string, min = 30): Promise<Arc[]> {
  const arcs: Arc[] = JSON.parse(await readFile(path, 'utf8'));

  return arcs
    .filter(({ chapters }) => chapters.length >= min)
    .map((arc) => ({
      ...arc,
      title: `${arc.title} (${arc.chapters.length} ch.)`
    }));
}

DanDaDan

A deliberately strange mix of pink, green, and occult purple.

import { readFile } from 'node:fs/promises';

type Arc = { title: string; chapters: number[] };

/** Load every arc and keep the ones worth binge-reading. */
export async function longArcs(path: string, min = 30): Promise<Arc[]> {
  const arcs: Arc[] = JSON.parse(await readFile(path, 'utf8'));

  return arcs
    .filter(({ chapters }) => chapters.length >= min)
    .map((arc) => ({
      ...arc,
      title: `${arc.title} (${arc.chapters.length} ch.)`
    }));
}

Sailor Moon

The first light variant, with warm cream and softened pastels.

import { readFile } from 'node:fs/promises';

type Arc = { title: string; chapters: number[] };

/** Load every arc and keep the ones worth binge-reading. */
export async function longArcs(path: string, min = 30): Promise<Arc[]> {
  const arcs: Arc[] = JSON.parse(await readFile(path, 'utf8'));

  return arcs
    .filter(({ chapters }) => chapters.length >= min)
    .map((arc) => ({
      ...arc,
      title: `${arc.title} (${arc.chapters.length} ch.)`
    }));
}

Death Note

Ink-black, off-white, apple red, and shinigami gold at AAA contrast.

1,089 installs so far

VS Code MarketplaceVS Code
695
Open VSXCursor, VSCodium, and Windsurf
394

Version 1.3.0, updated Sep 30, 2026. Open VSX counts downloads, so updates add to its total.

Next arc

What I'd
do next.

  • Extend the contrast check from text to UI states, such as selection and diff backgrounds.
  • Evaluate APCA alongside WCAG 2. WCAG 2 ratios misjudge some pairs on dark backgrounds.
  • Generate the Marketplace screenshots from the same editor renderer, replacing the hand-made ones.