A semantic color theming library for .NET applications with 38 themes, intelligent color mapping, and framework integration.
ktsu.ThemeProvider is a comprehensive theming system that uses semantic color specifications rather than arbitrary color names. Instead of hardcoding colors like "blue" or "red", you define colors by their purpose (Primary, Error, Warning) and priority level, and the library generates consistent, accessible color palettes. It includes 38 carefully crafted themes from popular color schemes and provides built-in Dear ImGui integration with an extensible architecture for other UI frameworks.
- Semantic Color System: Define colors by purpose (Primary, Error, Warning, Neutral) and priority level rather than specific hues, enabling consistent theming across any UI framework
- 38 Built-in Themes: Includes Catppuccin, Tokyo Night, Gruvbox, Everforest, Nightfox, Kanagawa, PaperColor, Nord, Dracula, VSCode, One Dark, Monokai, and Nightfly theme families
- Centralized Theme Registry: Discover, filter, and instantiate themes by name, family, or light/dark classification with rich metadata
- Dear ImGui Integration: Companion package
ktsu.ThemeProvider.ImGuiprovides complete ImGui color palette mapping viaImGuiPaletteMapper - Perceptual Color Science: Uses Oklab perceptual color space for uniform color interpolation, extrapolation, and lightness-based priority mapping
- WCAG Accessibility: Built-in contrast ratio calculations, accessibility level checking (AA/AAA), and automatic color adjustment to meet WCAG standards
- Extensible Framework Mappers: Implement
IPaletteMapper<TColorKey, TColorValue>to integrate with any UI framework - Priority-Based Color Hierarchy: Seven priority levels (VeryLow to VeryHigh) automatically mapped to appropriate lightness values, with theme-aware ordering for dark and light themes
- Multi-Target Support: Targets .NET 5.0 through 10.0, plus .NET Standard 2.0 and 2.1
Install-Package ktsu.ThemeProviderdotnet add package ktsu.ThemeProvider<PackageReferenceInclude="ktsu.ThemeProvider"Version="x.y.z" />For Dear ImGui integration, also install:
dotnet add package ktsu.ThemeProvider.ImGuiusingktsu.ThemeProvider;usingstaticktsu.ThemeProvider.ThemeRegistry;// Create a theme directlyvartheme=newThemes.Catppuccin.Mocha();// Or find and create via the registryThemeInfo?themeInfo=FindTheme("Catppuccin Mocha");ISemanticTheme?registryTheme=themeInfo?.CreateInstance();// Map semantic color requests to actual colorsvarrequests=new[]{newSemanticColorRequest(SemanticMeaning.Primary,Priority.Medium),newSemanticColorRequest(SemanticMeaning.Error,Priority.High),newSemanticColorRequest(SemanticMeaning.Neutral,Priority.VeryLow),};IReadOnlyDictionary<SemanticColorRequest,Color>colors=SemanticColorMapper.MapColors(requests,theme);usingktsu.ThemeProvider;usingstaticktsu.ThemeProvider.ThemeRegistry;// Browse all themesIReadOnlyList<ThemeInfo>allThemes=AllThemes;IReadOnlyList<ThemeInfo>darkThemes=DarkThemes;IReadOnlyList<ThemeInfo>lightThemes=LightThemes;// Browse by familyIReadOnlyList<string>families=Families;IReadOnlyList<ThemeInfo>catppuccinThemes=GetThemesInFamily("Catppuccin");// Find a specific theme by name (case-insensitive)ThemeInfo?themeInfo=FindTheme("Tokyo Night Storm");ISemanticTheme?theme=themeInfo?.CreateInstance();// Create all theme instances at onceIReadOnlyList<ISemanticTheme>allInstances=CreateAllThemeInstances();IReadOnlyList<ISemanticTheme>gruvboxInstances=CreateThemeInstancesInFamily("Gruvbox");usingktsu.ThemeProvider;usingktsu.Semantics.Color;vartheme=newThemes.Nord.Nord();// Generate the complete palette (all meaning + priority combinations)IReadOnlyDictionary<SemanticColorRequest,Color>completePalette=SemanticColorMapper.MakeCompletePalette(theme);// Access any color from the palettevarprimaryMedium=completePalette[newSemanticColorRequest(SemanticMeaning.Primary,Priority.Medium)];stringhex=primaryMedium.ToHex();usingktsu.ThemeProvider;usingktsu.ThemeProvider.ImGui;usingHexa.NET.ImGui;// Create theme and mappervartheme=newThemes.Catppuccin.Mocha();varmapper=newImGuiPaletteMapper();// Get complete ImGui color paletteIReadOnlyDictionary<ImGuiCol,Vector4>imguiColors=mapper.MapTheme(theme);// Apply to ImGui stylevarstyle=ImGui.GetStyle();foreach((ImGuiColcolorKey,Vector4colorValue)inimguiColors){style.Colors[(int)colorKey]=colorValue;}usingktsu.ThemeProvider;usingktsu.Semantics.Color;Colorforeground=Color.FromHex("#FFFFFF");Colorbackground=Color.FromHex("#1E1E2E");// Calculate contrast ratio (1.0 .. 21.0)doublecontrastRatio=foreground.ContrastRatio(background);// Check WCAG complianceAccessibilityLevellevel=foreground.AccessibilityLevelAgainst(background,largeText:false);// Adjust a color to meet accessibility requirementsColoradjusted=foreground.AdjustForContrast(background,AccessibilityLevel.AA);// Create perceptually uniform gradientsIReadOnlyList<Color>gradient=foreground.Gradient(background,steps:10);Implement IPaletteMapper<TColorKey, TColorValue> to integrate with any UI framework:
usingktsu.ThemeProvider;publicclassMyFrameworkMapper:IPaletteMapper<MyColorEnum,MyColorType>{publicstringFrameworkName=>"My UI Framework";publicIReadOnlyDictionary<MyColorEnum,MyColorType>MapTheme(ISemanticThemetheme){varrequests=newDictionary<MyColorEnum,SemanticColorRequest>{{MyColorEnum.Button,new(SemanticMeaning.Primary,Priority.Medium)},{MyColorEnum.Background,new(SemanticMeaning.Neutral,Priority.VeryLow)},{MyColorEnum.ErrorText,new(SemanticMeaning.Error,Priority.High)},};varpalette=SemanticColorMapper.MapColors(requests.Values,theme);varresult=newDictionary<MyColorEnum,MyColorType>();foreach(varkvpinrequests){if(palette.TryGetValue(kvp.Value,outvarcolor)){result[kvp.Key]=ConvertToMyColor(color.RgbValue);}}returnresult;}}Declare your palette as a SemanticPalette and let it build the mapping. Colors are written as hex
strings, the notation upstream color schemes publish, so a theme can be diffed against its source:
usingktsu.ThemeProvider;usingktsu.Semantics.Color;usingSystem.Collections.ObjectModel;publicclassMyCustomTheme:ISemanticTheme{privatestaticreadonlySemanticPalettePalette=new(){// Neutrals are a ramp; the mapper interpolates between them across priority levels.Neutrals=["#C0CAF5","#1A1B26"],Primary="#7AA2F7",Alternate="#BB9AF7",Success="#9ECE6A",CallToAction="#9ECE6A",Information="#7DCFFF",Caution="#FF9E64",Warning="#E0AF68",Error="#F7768E",Failure="#F7768E",Debug="#BB9AF7",};publicDictionary<SemanticMeaning,Collection<Color>>SemanticMapping=>Palette.ToSemanticMapping();publicboolIsDarkTheme=>true;}SemanticMapping is the only contract, so a theme with unusual needs (more than two neutrals, or a
meaning driven by something other than a fixed hex) can still build the dictionary itself.
Defines semantic color purposes.
| Value | Description |
|---|---|
Neutral | Backgrounds, borders, inactive elements |
Primary | Main brand/accent colors |
Alternate | Secondary accent, binary choice emphasis |
Success | Successful operations, confirmations |
CallToAction | Important buttons and highlights demanding attention |
Information | Informational content, help text |
Caution | Cautionary content needing attention |
Warning | Warning states, potentially problematic |
Error | Error states, incorrect conditions |
Failure | Failed operations (distinct from error) |
Debug | Debug/development information |
Controls color intensity and lightness within a semantic meaning.
| Value | Description |
|---|---|
VeryLow | Lowest intensity (backgrounds in dark themes, lightest in light themes) |
Low | Low intensity |
MediumLow | Below-medium intensity |
Medium | Default intensity level |
MediumHigh | Above-medium intensity |
High | High intensity |
VeryHigh | Highest intensity (foreground text in dark themes, darkest in light themes) |
A readonly record struct combining a SemanticMeaning and Priority to specify a color.
| Property | Type | Description |
|---|---|---|
Meaning | SemanticMeaning | The semantic purpose of the color |
Priority | Priority | The intensity/lightness level |
Static class that maps semantic color requests to actual colors.
| Name | Return Type | Description |
|---|---|---|
MapColors(requests, theme) | IReadOnlyDictionary<SemanticColorRequest, Color> | Maps a collection of requests to colors using the theme |
MakeCompletePalette(theme) | IReadOnlyDictionary<SemanticColorRequest, Color> | Generates all possible meaning+priority combinations for a theme |
Static class providing centralized theme discovery and management.
| Name | Type | Description |
|---|---|---|
AllThemes | IReadOnlyList<ThemeInfo> | All 38 registered themes with metadata |
DarkThemes | IReadOnlyList<ThemeInfo> | All dark themes |
LightThemes | IReadOnlyList<ThemeInfo> | All light themes |
Families | IReadOnlyList<string> | All theme family names |
ThemesByFamily | IReadOnlyDictionary<string, IReadOnlyList<ThemeInfo>> | Themes grouped by family |
| Name | Return Type | Description |
|---|---|---|
FindTheme(name) | ThemeInfo? | Finds a theme by name (case-insensitive) |
GetThemesInFamily(family) | IReadOnlyList<ThemeInfo> | Gets all themes in a family |
CreateAllThemeInstances() | IReadOnlyList<ISemanticTheme> | Creates instances of all themes |
CreateThemeInstancesInFamily(family) | IReadOnlyList<ISemanticTheme> | Creates instances of themes in a family |
Colors are represented by the Color type from the ktsu.Semantics.Color package (linear RGB + alpha, gamma-correct). It is the currency type for themes (ISemanticTheme.SemanticMapping) and SemanticColorMapper.
Common members:
| Member | Description |
|---|---|
Color.FromHex(hex) | Creates a color from an sRGB hex string (proper sRGB→linear decode) |
ToHex() / ToBytes() | Converts back to an sRGB hex string / 8-bit channels |
ToSrgbVector4() | sRGB-encoded Vector4 for UI frameworks (e.g. ImGui) |
ToOklab() / ToOklch() | Perceptual (Oklab / polar LCh) representations |
ContrastRatio(other) | WCAG contrast ratio (1:1 .. 21:1) |
AccessibilityLevelAgainst(bg, largeText) | WCAG AA/AAA compliance (returns AccessibilityLevel) |
AdjustForContrast(bg, level, largeText) | Adjusts lightness to meet a WCAG level |
DistanceTo(other) | Perceptual (Oklab) distance |
MixOklab(other, t) / Gradient(to, steps) | Perceptual blend / uniform gradient |
Migration note (v2.0): ThemeProvider's in-house
RgbColor,SRgbColor,OklabColor,PerceptualColor, andColorMathtypes were removed in favour ofktsu.Semantics.Color. This also fixed a long-standing sRGB-as-linear gamma bug — base theme colors render identically, but the semantic mapper's derived colors and accessibility numbers are now computed correctly.
Interface for mapping semantic themes to framework-specific color palettes.
| Name | Type | Description |
|---|---|---|
FrameworkName | string | The name of the target UI framework |
| Name | Return Type | Description |
|---|---|---|
MapTheme(theme) | IReadOnlyDictionary<TColorKey, TColorValue> | Maps a theme to a framework-specific palette |
| Family | Variants | Description |
|---|---|---|
| Catppuccin | Latte, Frappe, Macchiato, Mocha | Warm pastel themes with excellent readability |
| Tokyo Night | Night, Storm, Day | Clean themes inspired by Tokyo's neon nights |
| Gruvbox | Dark, Dark Hard, Dark Soft, Light, Light Hard, Light Soft | Retro groove colors with warm backgrounds |
| Everforest | Dark, Dark Hard, Dark Soft, Light, Light Hard, Light Soft | Green forest colors for comfortable viewing |
| Nightfox | Nightfox, Dayfox, Duskfox, Nordfox, Terafox, Carbonfox, Dawnfox | Fox-inspired vibrant themes |
| Kanagawa | Wave, Dragon, Lotus | Japanese-inspired themes |
| PaperColor | Light, Dark | Material Design inspired themes |
| VSCode | Dark, Light | Microsoft VSCode default themes |
| Nord | - | Arctic-inspired theme with cool blue tones |
| Dracula | - | Gothic theme with purple and pink accents |
| One Dark | - | Atom's iconic One Dark theme |
| Monokai | - | Classic Monokai with vibrant colors |
| Nightfly | - | Dark blue theme inspired by night flying |
Contributions are welcome! Feel free to open issues or submit pull requests.
This project is licensed under the MIT License. See the LICENSE.md file for details.