Candela is a warm, low-fatigue theme for Zed where every color is derived, not picked — uniform in OKLCH, verified against WCAG. Beautiful and legible, not one at the cost of the other.
There are two ways to judge a color theme, and they usually fight each other:
Colors that feel like one family: same weight, evenly spaced, none screaming louder than the rest. Solarized, Nord and Dracula nail this — but many are hard to read.
Legibility you can prove with a number (WCAG ratio). GitHub Primer and IBM Carbon are legible — but their palettes are assembled ad-hoc, so colors clash.
Candela is engineered to pass both: accents are generated in OKLCH at a fixed lightness and chroma so they're perceptually uniform, and the result is checked with WCAG math before a single line ships.
1// color = identifier, gray = operator 2import { readFile } from "node:fs/promises"; 3 4interface Theme { 5 name: string; 6 contrast: number; 7} 8 9async function load(path: string): Promise<Theme> { 10 const raw = await readFile(path, "utf8"); 11 if (JSON.parse(raw).contrast < 7) 12 throw new Error(`below AAA: ${path}`); 13 return JSON.parse(raw); 14}
1# color = identifier, gray = operator 2from pathlib import Path 3import json 4 5class Theme: 6 def __init__(self, name: str, contrast: float): 7 self.name = name 8 self.contrast = contrast 9 10def load(path: Path) -> Theme: 11 data = json.loads(path.read_text()) 12 if data["contrast"] < 7: 13 raise ValueError(f"below AAA: {path}") 14 return Theme(**data)
1// color = identifier, gray = operator 2use std::fs; 3 4struct Theme { 5 name: String, 6 contrast: f64, 7} 8 9fn load(path: &str) -> Result<Theme, Error> { 10 let raw = fs::read_to_string(path)?; 11 let theme: Theme = from_str(&raw)?; 12 assert!(theme.contrast >= 7.0, "below AAA"); 13 Ok(theme) 14}
1/* warm background, cool structure */ 2:root { 3 --bg: #1c1915; 4 --text: #e8e4df; 5} 6 7.editor { 8 background: var(--bg); 9 color: var(--text); 10 font-size: 14px; 11 line-height: 1.7; 12}
Variables, props and params are colored; operators and punctuation fade to neutral gray. Your eye lands on the content, not the syntax noise.
Warm background eases eye strain; cool accents mark structure (functions, types), warm accents mark values (numbers, strings). Calm to read, easy to scan.
Meaning never depends on red vs green. Red is reserved for errors and always paired with a background and border, so status reads in any vision type.
Both variants share the same OKLCH construction and role mapping — switch by time of day without relearning your colors.
Full 16-color ANSI palette derived from the same accents, plus bright/dim variants. Your integrated terminal matches the editor.
Avoids halation and glare. Backgrounds and text sit just inside the extremes, which is gentler on OLED and in dim rooms.
| Theme | Perceptual coherence | Measured contrast |
|---|---|---|
| Solarized / Nord / Dracula | ✓ strong | ✗ often fails AA |
| GitHub Primer / IBM Carbon | ✗ ad-hoc | ✓ passes |
| Candela | ✓ OKLCH-uniform | ✓ AAA / AA+ |
| Role | Ratio | Grade |
|---|---|---|
| Body | 14:1 | AAA |
| Variable | 9.0:1 | AAA |
| Function | 9.4:1 | AAA |
| Type | 9.6:1 | AAA |
| String | 9.8:1 | AAA |
| Number | 8.9:1 | AAA |
| Role | Ratio | Grade |
|---|---|---|
| Body | 13:1 | AAA |
| Variable | 6.1:1 | AA+ |
| Function | 5.6:1 | AA+ |
| Type | 5.1:1 | AA+ |
| String | 5.4:1 | AA+ |
| Number | 5.8:1 | AA+ |
Command palette → zed: extensions → search Candela → Install.
Then Ctrl+K Ctrl+T and pick Candela Dark or Candela Light.
Drop themes/candela.json into:
Windows %APPDATA%\Zed\themes\
macOS / Linux ~/.config/zed/themes/
OKLCH is a perceptually uniform color space. Fixing lightness and chroma while rotating hue gives colors that truly feel equally bright and equally vivid — something HSL and hex-picking can't guarantee. That's what stops one accent from screaming over the others.
Body text is AAA (≥7:1) in both dark and light. Accents are AAA in dark and AA+ (≥4.5:1, most ≥5:1) in light — a deliberate trade so light-mode colors stay saturated enough to tell apart. Every value is computed, not estimated.
Yes. Zed uses Tree-sitter, so highlighting is grammar-based and consistent across languages. The role mapping (identifier = color, operator = gray) applies everywhere; the samples above are the same theme, unedited.
No. Both ship in one extension and share the same construction, so switching keeps every role in the same color family.
MIT. Fork it, tweak it, ship your own spin.