# ThemeEx
[](https://hex.pm/packages/theme_ex)
[](https://hexdocs.pm/theme_ex)
[](https://github.com/pepicrft/theme_ex/actions)
[](https://github.com/pepicrft/theme_ex/blob/main/LICENSE)
An Elixir package that implements data structures for the [Theme UI theme specification](https://theme-ui.com/theme-spec) and provides utilities for parsing, validation, and CSS variable generation.
## Features
- **Complete Theme UI Implementation**: Full support for Theme UI specification including colors, fonts, typography scales, and design tokens
- **Type-Safe Data Structures**: Elixir structs with proper type specifications for all theme components
- **JSON Parsing & Validation**: Parse theme JSON with comprehensive error handling and validation
- **Built-in JSON Schema Generation**: Generate JSON Schema for theme validation without external dependencies
- **CSS Variables Generation**: Convert themes to CSS custom properties with consistent naming conventions
- **Color Modes Support**: Handle multiple color modes (light, dark, custom) with nested color definitions
- **Responsive Design Tokens**: Support for breakpoints, typography scales, spacing, and size systems
- **Comprehensive Validation**: Custom validation logic that ensures theme integrity and type safety
## Installation
Add `theme_ex` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:theme_ex, "~> 0.1.0"}
]
end
```
## Data Structures
ThemeEx implements the complete Theme UI specification with the following core structures:
- `ThemeEx.Theme` - Main theme container
- `ThemeEx.Colors` - Color palette with semantic colors and modes
- `ThemeEx.Fonts` - Font family definitions
- `ThemeEx.FontWeights` - Font weight specifications
- `ThemeEx.LineHeights` - Line height definitions
## Usage
### Creating Themes
```elixir
# Create a complete theme
theme = %ThemeEx.Theme{
colors: %ThemeEx.Colors{
text: "#000000",
background: "#ffffff",
primary: "#0066cc",
secondary: "#ff6600",
modes: %{
"dark" => %{
"text" => "#ffffff",
"background" => "#000000",
"primary" => "#66ccff"
}
}
},
fonts: %ThemeEx.Fonts{
body: "system-ui, -apple-system, sans-serif",
heading: "Georgia, serif",
monospace: "Menlo, Monaco, monospace"
},
fontWeights: %ThemeEx.FontWeights{
body: 400,
heading: 700,
bold: 600
},
fontSizes: [12, 14, 16, 20, 24, 32, 48, 64, 96],
space: [0, 4, 8, 16, 32, 64, 128, 256],
breakpoints: ["40em", "52em", "64em"]
}
```
### JSON Parsing
```elixir
# Parse from JSON string
json_theme = """
{
"colors": {
"text": "#000000",
"background": "#ffffff",
"primary": "#0066cc",
"modes": {
"dark": {
"text": "#ffffff",
"background": "#000000"
}
}
},
"fonts": {
"body": ["system-ui", "sans-serif"],
"heading": "Georgia, serif"
},
"fontSizes": [12, 14, 16, 20, 24, 32]
}
"""
{:ok, theme} = ThemeEx.from_json(json_theme)
# Or from a map
theme_map = %{
"colors" => %{"primary" => "#0066cc"},
"fontSizes" => [14, 16, 18, 24]
}
{:ok, theme} = ThemeEx.from_map(theme_map)
```
### CSS Variables Generation
```elixir
theme = %ThemeEx.Theme{
colors: %ThemeEx.Colors{
primary: "#0066cc",
secondary: "#ff6600"
},
fonts: %ThemeEx.Fonts{
body: "system-ui, sans-serif"
},
fontSizes: [12, 14, 16, 20]
}
css = ThemeEx.to_css_variables(theme)
# Output:
# :root {
# --theme-colors-primary: #0066cc;
# --theme-colors-secondary: #ff6600;
# --theme-fonts-body: system-ui, sans-serif;
# --theme-fontSizes-0: 12;
# --theme-fontSizes-1: 14;
# --theme-fontSizes-2: 16;
# --theme-fontSizes-3: 20;
# }
```
### Theme Validation
```elixir
# Validate theme structure and types
case ThemeEx.validate(theme) do
{:ok, validated_theme} ->
IO.puts("Theme is valid!")
validated_theme
{:error, errors} ->
IO.puts("Validation failed: #{inspect(errors)}")
end
```
### JSON Schema Generation
```elixir
# Generate JSON Schema for external validation
schema = ThemeEx.json_schema()
# Returns a complete JSON Schema following draft/2020-12 specification
%{
"$schema" => "https://json-schema.org/draft/2020-12/schema",
"title" => "Theme UI Theme",
"type" => "object",
"properties" => %{
"colors" => %{...},
"fonts" => %{...},
# ... complete schema definition
}
}
```
## Advanced Features
### Font Arrays and Stacks
```elixir
# Fonts can be defined as strings or arrays
fonts = %ThemeEx.Fonts{
body: ["Helvetica Neue", "Arial", "sans-serif"],
heading: "Georgia, serif"
}
```
### Color Arrays and Scales
```elixir
# Colors support both single values and scales
colors = %ThemeEx.Colors{
primary: ["#e3f2fd", "#bbdefb", "#90caf9", "#64b5f6", "#42a5f5"],
text: "#000000"
}
```
### Responsive Design Tokens
```elixir
theme = %ThemeEx.Theme{
space: [0, 4, 8, 16, 32, 64, 128], # Spacing scale
sizes: [16, 32, 64, 128, 256, 512, 768], # Size scale
radii: [0, 2, 4, 8, 16], # Border radius scale
shadows: [ # Box shadow definitions
"none",
"0 1px 3px rgba(0,0,0,0.12)",
"0 4px 6px rgba(0,0,0,0.16)"
],
breakpoints: ["40em", "52em", "64em"] # Media query breakpoints
}
```
## API Reference
### Core Functions
- `ThemeEx.from_json/1` - Parse theme from JSON string
- `ThemeEx.from_map/1` - Convert map to theme struct
- `ThemeEx.to_css_variables/1` - Generate CSS custom properties
- `ThemeEx.json_schema/0` - Generate JSON Schema for validation
- `ThemeEx.validate/1` - Validate theme structure and types
### Data Structures
All structs include proper type specifications and support for both single values and arrays where appropriate by the Theme UI specification.
## Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Run tests (`mix test`)
4. Commit your changes (`git commit -m 'Add amazing feature'`)
5. Push to the branch (`git push origin feature/amazing-feature`)
6. Open a Pull Request
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## References
- [Theme UI Specification](https://theme-ui.com/theme-spec)
- [JSON Schema Specification](https://json-schema.org/)
- [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*)