This skill generates comprehensive RStudio .rstheme dark themes from a single primary hex color using OKLCH palette derivation. The governing principle is complete coverage — every customizable UI region in RStudio must receive themed selectors so no default light surfaces leak through in dark mode. Begin with <step_1_derive_palette> to generate the foundational color scales.
<use_cases>
- •Generate a new RStudio dark theme from a primary hex color with full coverage across all 29 UI regions
- •Add coverage for missing UI regions (dialogs, terminal, menus) to an existing
.rsthemethat only covers the Ace editor - •Audit an existing
.rsthemefor selector coverage gaps and contrast failures using dual-check verification - •Adapt a VS Code Radiant Teal palette to RStudio format while maintaining cross-editor semantic consistency
- •Regenerate a theme after upstream RStudio version changes break selectors or introduce new UI regions
</use_cases>
<workflow>Execute steps sequentially. Each step produces output that feeds the next — palette feeds semantic mapping, semantic mapping feeds CSS generation, CSS sections combine into the final .rstheme file.
<step_1_derive_palette>
Generate six 12-step Radix scales from the primary hex using OKLCH color space via coloraide.
Load color-derivation.instructions.md for: OKLCH-first derivation rules, Radix 12-step architecture, coloraide usage patterns, and contrast enforcement requirements.
Required scales:
| Scale | Purpose | Hue derivation |
|---|---|---|
| primary | Accents, selection, cursor, links, active states | Primary hex hue (constant) |
| neutral | Backgrounds, borders, text, gutter, surfaces | Primary hue with chroma 0.005–0.02 |
| red | Errors, invalid syntax, deletions, git removed | Hue rotation to ~25° |
| amber | Warnings, deprecated syntax, modified indicators | Hue rotation to ~70° |
| green | Success states, git additions, string literals | Hue rotation to ~145° |
| blue | Info states, links, type annotations | Hue rotation to ~245° |
Derivation rules:
- •Hold hue constant within each scale — vary only L and C across the 12 steps
- •Dark mode lightness range: step 1 ~L 0.13–0.15, step 12 ~L 0.93–0.97
- •Chroma curve: low (steps 1–2), rising (steps 3–8), peak (steps 9–10), moderate (steps 11–12)
- •Apply
coloraidegamut mapping (fit('srgb')) on every OKLCH→sRGB conversion - •Output all hex values in 6-digit lowercase format
</step_1_derive_palette>
<step_2_map_semantic_roles>
Assign palette scale steps to UI semantic roles following the cross-editor mapping.
Load theme-conventions.instructions.md for: the <semantic_mapping> table and RStudio-specific format constraints.
Core role assignments:
| Semantic role | Scale/step | CSS target |
|---|---|---|
| App background (deepest) | neutral/1 | .ace_gutter background |
| Editor background | neutral/2 | .ace_editor background |
| Component background | neutral/3 | Toolbar, sidebar, panel backgrounds |
| Hover state | neutral/4 | .ace_marker-layer .ace_active-line |
| Selection | primary/4 | .ace_marker-layer .ace_selection |
| Subtle border | neutral/6 | Panel dividers, gutter border |
| Default border | neutral/7 | Input borders, dropdown borders |
| Strong border | neutral/8 | Focus rings |
| Accent solid | primary/9 | Active indicators, buttons |
| Accent hover | primary/10 | Cursor, links, hover accents |
| Secondary text | neutral/11 | Line numbers, placeholder text |
| Primary text | neutral/12 | Editor foreground, UI labels |
Constraint: Every foreground/background pair formed by these assignments must pass dual-check contrast before proceeding — verify in <step_8_audit_contrast>.
</step_2_map_semantic_roles>
<step_3_generate_ace_css>
Generate Ace editor CSS for code syntax highlighting. This is the core editing surface.
Token selectors and their scale assignments:
| Token | Selector | Typical scale |
|---|---|---|
| Keywords | .ace_keyword | primary/9 |
| Strings | .ace_string | green/9 |
| Constants | .ace_constant, .ace_constant.ace_language, .ace_constant.ace_numeric | amber/9 |
| Comments | .ace_comment | neutral/11 (desaturated) |
| Functions | .ace_support.ace_function | blue/9 |
| Types/classes | .ace_support.ace_class, .ace_storage.ace_type | primary/11 |
| Operators | .ace_keyword.ace_operator | neutral/12 |
| Variables | .ace_variable, .ace_identifier | neutral/12 |
| Tags (HTML/XML) | .ace_meta.ace_tag, .ace_entity.ace_name.ace_tag | red/9 |
| Attributes | .ace_entity.ace_other.ace_attribute-name | amber/11 |
| Support/built-in | .ace_support, .ace_support.ace_type | blue/11 |
| Invalid | .ace_invalid | red/9 bg with neutral/12 fg |
| Deprecated | .ace_invalid.ace_deprecated | amber/9 with strikethrough |
| Headings (Rmd) | .ace_markup.ace_heading | primary/10 bold |
Critical constraint: NEVER use !important on Ace syntax selectors — Ace applies specificity correctly and !important causes cascading conflicts with RStudio's built-in style management.
</step_3_generate_ace_css>
<step_4_generate_ide_chrome_css>
Generate CSS for the GWT-based IDE chrome — all non-editor UI surfaces.
Load references/rstudio-selectors.md for: the complete selector catalog organized by UI region. JIT-load this reference when mapping selectors per region.
UI regions to cover (13 regions):
- •Toolbar — main toolbar, project toolbar, button containers
- •Sidebar — files pane, environment pane, history pane
- •Menu bar — top menu, dropdown menus, menu items, separators
- •Status bar — bottom status indicators
- •Scrollbar — all scrollbar tracks, thumbs, and corners
- •Tabs — editor tabs, pane tabs, active/inactive/hover states
- •Panels — output panel, console panel, viewer panel
- •Environment pane — variable browser, data viewer grid
- •File pane — file browser, path bar, filter controls
- •Help pane — help viewer, search results, topic display
- •Plots pane — plot output area, navigation controls
- •Packages pane — package list, install controls, checkboxes
- •Connections pane — connection browser, status indicators
Critical constraint: ALWAYS use !important on IDE chrome selectors — GWT applies inline styles that override normal CSS specificity. Without !important, chrome styles are silently ignored.
Selector pattern: Use only stable, documented selector patterns — .rstheme_ prefixed classes, #rstudio_ ID selectors, and role-based attribute selectors. Never use obfuscated GWT class names.
</step_4_generate_ide_chrome_css>
<step_5_generate_dialog_css>
Generate CSS for all dialog boxes and modal overlays. Missing dialog styles are the most common cause of light surfaces in dark themes.
Dialog categories (~50 selectors):
- •Preferences dialog — settings panels, category list, input controls
- •New file dialog — file type selector, name input, template preview
- •Git commit dialog — diff viewer, commit message, staged files list
- •Install packages dialog — package search, repository selector, install button
- •Search/replace dialog — find input, replace input, match controls, result highlights
- •About dialog — version info, credits panel
- •Keyboard shortcuts dialog — shortcut list, filter input, category headers
- •Global options dialog — appearance, code editing, pane layout settings
- •Project options dialog — build tools, environments, version control settings
Critical constraint: ALWAYS use !important on dialog selectors — dialogs render via GWT overlay panels with inline styles.
Common selector patterns:
- •
.gwt-DialogBox— outer dialog container - •
.gwt-DialogBox .dialogMiddleCenter— dialog content area - •
.gwt-DialogBox .dialogTopCenter— dialog title bar - •
.rstudio-themes-dark-menus .gwt-PopupPanel— popup overlay panels - •
.gwt-TabLayoutPanelTab— tabbed dialog panels
</step_5_generate_dialog_css>
<step_6_generate_terminal_css>
Generate CSS for the xterm.js-based terminal emulator.
Terminal surface selectors:
- •
.terminal— terminal foreground and background - •
.xterm-viewport— scrollable terminal viewport - •
.xterm-screen— terminal screen area - •Terminal cursor —
.xterm-cursor-layercursor styling - •Terminal selection —
.xterm .xterm-selection divselection highlight - •Terminal scrollbar —
.xterm-viewport::-webkit-scrollbarand thumb
ANSI 16 colors — map to palette scales:
| ANSI color | Normal scale/step | Bright scale/step |
|---|---|---|
| Black | neutral/1 | neutral/5 |
| Red | red/9 | red/10 |
| Green | green/9 | green/10 |
| Yellow | amber/9 | amber/10 |
| Blue | blue/9 | blue/10 |
| Magenta | primary/9 (hue ~320°) | primary/10 (hue ~320°) |
| Cyan | primary/9 | primary/10 |
| White | neutral/11 | neutral/12 |
xterm-256 extended palette (P3 — nice to have): Generate the 216-color cube (indices 16–231) and 24-step grayscale ramp (indices 232–255) using OKLCH lightness interpolation.
Critical constraint: ALWAYS use !important on terminal selectors — xterm.js applies inline styles for theming.
</step_6_generate_terminal_css>
<step_7_generate_extras_css>
Generate CSS for supplementary editor features that enhance the coding experience.
Feature selectors:
- •Rainbow parentheses —
.ace_paren_color_0through.ace_paren_color_6, use hue rotation from primary - •Matching brackets —
.ace_bracket, highlight with primary/8 border - •Find highlights —
.ace_marker-layer .ace_selected-word,.ace_search-result, use amber/4 background - •Marker bar —
.ace_marker-layer .ace_step, vertical scroll annotations - •Navigation banner —
.rstudio-themes-flat .rstheme_secondaryToolbar, breadcrumb and scope navigation - •Indent guides —
.ace_indent-guide, subtle neutral/4 border - •Fold widgets —
.ace_gutter-cell .ace_fold-widget, collapse/expand indicators in gutter - •Chunk backgrounds —
.ace_marker-layer .ace_foreign_chunk_bg, background for embedded code chunks (R Markdown) - •Console output — differentiate standard output, error output, and message output in the R console
Constraint: Use !important only on non-Ace chrome selectors (navigation banner, marker bar). Ace editor extras (rainbow parens, brackets, fold widgets) follow the no-!important rule from <step_3_generate_ace_css>.
</step_7_generate_extras_css>
<step_8_audit_contrast>
Run WCAG 2.1 AA + APCA dual-check on all foreground/background pairs generated in steps 2–7.
Load contrast-audit/SKILL.md for: the complete pair collection, classification, calculation, and reporting workflow.
Threshold quick reference:
| Context | WCAG minimum | APCA minimum | APCA preferred |
|---|---|---|---|
| Body text (code, comments) | 4.5:1 | Lc 75 | Lc 90 |
| Large text (headings, titles) | 3:1 | Lc 60 | Lc 75 |
| UI components (buttons, tabs) | 3:1 | Lc 60 | Lc 75 |
| Sub-text (line numbers, hints) | 3:1 | Lc 45 | Lc 60 |
| Non-text (borders, indicators) | 3:1 | Lc 30 | Lc 45 |
Remediation: When a pair fails contrast, adjust lightness in OKLCH — NEVER adjust hue or chroma to fix contrast. Increase foreground L or decrease background L until both thresholds pass.
Radix scale validation: Verify step 11 vs step 2 achieves Lc >= 60 and step 12 vs step 2 achieves Lc >= 90 for every scale.
</step_8_audit_contrast>
<step_9_validate_css>
Validate the generated CSS for correctness and convention compliance before assembly.
Syntax validation:
- •Parse all CSS rules — no missing semicolons, no unclosed braces, no invalid property values
- •Verify all hex colors match
#[0-9a-f]{6}(6-digit lowercase, no shorthand) - •Confirm no duplicate selectors with conflicting declarations
Convention compliance:
- •
!importantpresent on ALL IDE chrome, dialog, and terminal selectors - •
!importantabsent from ALL Ace syntax selectors - •No obfuscated GWT class names (patterns like
.gwt-ABC123,.GNKJF3BCHB) — only stable selectors - •No
font-familyorfont-sizedeclarations — RStudio controls these via user preferences (exception:@font-facefor Server mode)
Coverage completeness:
- •Cross-reference generated selectors against the selector catalog in references/rstudio-selectors.md
- •Flag any region from the catalog that has zero selectors in the output
- •Minimum coverage: all 13 IDE chrome regions from
<step_4_generate_ide_chrome_css>plus Ace editor and terminal
</step_9_validate_css>
<step_10_assemble_rstheme>
Combine all CSS sections into a single .rstheme file.
File structure:
- •Metadata comment header — theme name, version, primary hex, generation date, generator script
- •Ace editor base —
.ace_editorbackground/foreground,.ace_gutter,.ace_cursor - •Ace syntax tokens — all token selectors from
<step_3_generate_ace_css> - •Ace editor features — selection, active line, matching brackets, find highlights
- •IDE chrome — toolbar, sidebar, menu, tabs, panels from
<step_4_generate_ide_chrome_css> - •Dialog boxes — all modal and dialog selectors from
<step_5_generate_dialog_css> - •Terminal — xterm.js surface and ANSI colors from
<step_6_generate_terminal_css> - •Extras — rainbow parens, indent guides, fold widgets from
<step_7_generate_extras_css> - •Console — R console-specific styling (input, output, error, message)
- •Scrollbars — unified scrollbar styling across all panes
File naming: {Theme-Name}.rstheme — title case with hyphens, .rstheme extension.
Dark mode detection: RStudio auto-detects dark mode when .ace_editor background luminance < 0.2. Ensure the neutral/2 step used for editor background has OKLCH L < 0.20. Include rs-theme-name and rs-theme-is-dark comments as explicit overrides at the top of the file, even though RStudio auto-detects dark mode from luminance.
Installation verification: After generation, verify the file loads without error via rstudioapi::addTheme("path/to/Theme.rstheme").
</step_10_assemble_rstheme>
</workflow><important_rules>
Critical rules that govern all RStudio theme generation. Violations cause silent failures or broken themes.
- •
!importantsplit rule — ALWAYS use!importanton IDE chrome, dialog, and terminal selectors (GWT inline styles override normal specificity). NEVER use!importanton Ace editor syntax selectors (Ace handles specificity correctly, and!importantcauses cascading conflicts) - •No obfuscated GWT classes — NEVER target minified/obfuscated GWT class names like
.gwt-ABC123or.GNKJF3BCHB. These change between RStudio versions and break themes on update. Use only.rstheme_prefixed classes,.ace_classes,.xtermclasses,.rstudio-themes-dark-menus, and officially documented selectors - •Dark mode detection — RStudio auto-detects dark mode when
.ace_editorbackground has luminance < 0.2. Ensure neutral/2 has OKLCH L < 0.20. Includers-theme-nameandrs-theme-is-darkcomments as explicit overrides at the top of the file, even though RStudio auto-detects dark mode from luminance - •No font styling —
font-familyandfont-sizeare controlled by RStudio user preferences, not CSS. Do not set them in.rstheme(exception:@font-facedeclarations work in RStudio Server mode only) - •OKLCH-only derivation — All colors derive from the primary hex via OKLCH. Cross-reference color-derivation.instructions.md for pipeline rules. No hardcoded hex values anywhere
- •Selector catalog — The complete selector reference is in references/rstudio-selectors.md. JIT-load this file when mapping selectors per UI region — do not memorize or inline the full catalog
- •Hex format — All color values in 6-digit lowercase hex with
#prefix. No shorthand, no uppercase, no named colors, no alpha channels (RStudio CSS does not support#rrggbbaa)
</important_rules>
<pitfalls>Common mistakes that cause broken or incomplete RStudio themes.
- •Missing
!importanton GWT chrome — Styles are silently ignored because GWT applies inline styles with higher specificity. The theme appears to partially work but chrome surfaces remain default. Fix: add!importantto every non-Ace selector - •Using obfuscated GWT classes — Theme works on one RStudio version but breaks on the next update when GWT recompiles and reassigns class names. Fix: use only stable
.rstheme_,.ace_, and documented selectors - •Hardcoding colors instead of deriving from palette — Produces an inconsistent theme where some surfaces feel disconnected. Fix: trace every hex value back through the OKLCH pipeline from the primary hex
- •Missing dialog box selectors — The most visible dark theme failure: opening Preferences or Install Packages reveals a white modal over a dark editor. Fix: always include the ~50 dialog selectors from
<step_5_generate_dialog_css> - •Untested terminal ANSI colors — Certain ANSI color combinations produce invisible text (e.g., dark blue on dark background). Fix: verify all 16 ANSI colors against the terminal background with dual-check contrast
- •Setting
font-familyin CSS — Conflicts with the user's RStudio font preferences, causing unexpected font changes or fallback to system fonts. Fix: let RStudio manage fonts via its Appearance preferences - •Using HSL for palette math — HSL has non-uniform lightness perception, producing visible hue shifts at low lightness values typical of dark themes. Fix: use OKLCH for all derivation, convert to sRGB only for final hex output
<error_handling>
- •If a contrast check fails on a foreground/background pair, then adjust the foreground lightness (L) in OKLCH — increase L for light-on-dark, decrease L for dark-on-light. Never adjust hue or chroma to fix contrast. Re-run dual-check after adjustment
- •If a selector from the catalog is not found in the target RStudio version, then flag it as version-dependent, wrap it in a comment noting the minimum version, and exclude it from P1 coverage requirements
- •If CSS validation reports a syntax error, then check for missing semicolons, unclosed braces, or invalid property values. Common in generated CSS: missing semicolon on last property before closing brace
- •If the
.rsthemefile fails to load viarstudioapi::addTheme(), then verify: file has.rsthemeextension, CSS parses without syntax errors, no BOM characters at file start, file uses UTF-8 encoding - •If the editor background is not detected as dark mode, then check that neutral/2 has OKLCH L < 0.20 — if it is marginally above 0.20, reduce L by 0.01 increments until dark mode triggers
- •If a UI region has no stable selectors available, then return BLOCKED with the region name and RStudio version — do not fabricate selectors
</error_handling>
<validation>P1 — Blocking (must pass before delivery):
- •All Ace syntax token selectors present (keyword, string, constant, comment, function, type, operator, variable, tag, attribute, support, invalid, deprecated)
- •All 13 IDE chrome regions have at least one selector each
- •
!importantconvention correct: present on all chrome/dialog/terminal selectors, absent from all Ace syntax selectors - •No obfuscated GWT class names in any selector
- •All foreground/background pairs pass WCAG 2.1 AA + APCA dual-check
- •
.ace_editorbackground has OKLCH L < 0.20 (dark mode detection) - •All hex values are 6-digit lowercase format
- •No hardcoded colors — every value traces to the OKLCH pipeline
P2 — Quality (should pass):
- •All dialog box categories covered (preferences, new file, git, install packages, search/replace)
- •Terminal ANSI 16 colors complete with all normal/bright pairs
- •Rainbow parentheses included with 7 hue-rotated colors
- •Metadata comment header present with theme name, version, primary hex, and date
- •Radix scale step 11 vs step 2 achieves Lc >= 60 for all scales
- •Radix scale step 12 vs step 2 achieves Lc >= 90 for all scales
P3 — Polish (nice to have):
- •xterm-256 extended palette (216-color cube + 24-step grayscale)
- •Navigation banner and breadcrumb styling
- •Indent guides with subtle visual treatment
- •Fold widgets with themed expand/collapse indicators
- •Body text pairs reach preferred APCA Lc 90 (not just minimum Lc 75)
- •Console output differentiation (standard, error, message styling)
- •color-derivation.instructions.md — OKLCH pipeline rules, Radix scale architecture, coloraide usage, contrast enforcement
- •theme-conventions.instructions.md —
<semantic_mapping>table, RStudio format constraints, file naming conventions - •contrast-audit/SKILL.md — Contrast verification workflow: pair collection, classification, WCAG + APCA calculation, gap detection, reporting
- •references/rstudio-selectors.md — Complete CSS selector catalog organized by UI region (JIT-load when mapping selectors)
- •tools/generate_rstheme.py — Reference implementation: OKLCH palette derivation → 10-section CSS generation (~1636 lines)
- •rstudio-radiant/Dark-Radiant-Teal.rstheme — Reference output: complete themed
.rsthemewith 668 selectors across all UI regions