Convert SVG files to Kotlin Compose ImageVector code with high fidelity and production-ready output.
svg-to-compose-vector
is a Python command-line tool that transforms SVG graphics into Compose ImageVector Kotlin code. It supports advanced SVG features including paths, shapes, gradients, transforms, and strokes, generating clean, optimized Compose code.
Installation • Usage • Features • Templates • Multicolor Templates • Examples
- Complete SVG Support: Handles paths, basic shapes (rect, circle, ellipse, line, polygon), groups, transforms, gradients, and strokes
- High Fidelity Conversion: Mathematically precise shape-to-path conversion with proper coordinate handling
- Production-Ready Output: Generates clean Kotlin code following Compose best practices with optimal parameter usage
- Flexible Templates: Built-in templates for different use cases (val declarations, composable functions, icon objects)
- Advanced Color Support: Full support for hex, RGB, HSL, named colors, and gradients (linear and radial)
- Multicolor Template System: Intelligent color mapping with parameterized @Composable generation for theme-aware icons
- Smart Optimizations: Uses Compose built-in colors (Color.Red vs Color(0xFFFF0000)) and omits default parameters
- Comprehensive Error Handling: Clear warnings for unsupported SVG features with graceful degradation
- Batch Processing: Convert entire directories of SVG files at once
Convert an SVG file to Compose ImageVector code:
svg2compose convert icon.svg
Output:
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
ImageVector.Builder(
name = "Icon",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color.Black)) {
moveTo(12f, 2f)
lineTo(22f, 12f)
lineTo(12f, 22f)
lineTo(2f, 12f)
close()
}
}.build()
Create theme-aware icons with automatic color parameterization:
svg2compose convert icon.svg --multicolor-template theme.j2
Template (theme.j2):
{%- set color_mappings = {
"#FF0000": {"semantic_name": "errorColor", "replacement": "MaterialTheme.colorScheme.error"},
"#0000FF": "MaterialTheme.colorScheme.primary"
} -%}
@Composable
fun {{ name.name_part_pascal }}(
{%- for color_hex, mapping in color_mappings.items() if color_hex in used_colors and mapping.semantic_name is defined %}
{{ mapping.semantic_name }}: Color = {{ mapping.replacement }}{{ "," if not loop.last }}
{%- endfor %}
): ImageVector { /* ... */ }
Output:
@Composable
fun MyIcon(errorColor: Color = MaterialTheme.colorScheme.error): ImageVector {
return ImageVector.Builder(...).apply {
path(fill = SolidColor(errorColor)) { /* red elements */ }
path(fill = SolidColor(MaterialTheme.colorScheme.primary)) { /* blue elements */ }
}.build()
}
pip install svg-to-compose-vector
git clone https://github.com/chachako/svg-to-compose-vector.git
cd svg-to-compose-vector
uv sync
uv run python -m src.cli --help
git clone https://github.com/chachako/svg-to-compose-vector.git
cd svg-to-compose-vector
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -e .
svg2compose --help
# Convert to stdout
svg2compose convert icon.svg
# Convert to file
svg2compose convert icon.svg -o Icon.kt
# Specify icon name
svg2compose convert icon.svg -n HomeIcon
# List available templates
svg2compose templates
# Use specific template
svg2compose convert icon.svg -t composable_function -o HomeIcon.kt
# Use custom template file
svg2compose convert icon.svg -t my_template.j2 -o CustomIcon.kt
# Convert all SVG files in a directory
svg2compose batch icons/ output/
# With custom naming pattern
svg2compose batch icons/ output/ --naming "Icon{name}"
# Using specific template
svg2compose batch icons/ output/ -t composable_function
# Use multicolor template for theme-aware icons
svg2compose convert icon.svg --multicolor-template multicolor.j2
# Combine with regular templates (fallback when colors don't match)
svg2compose convert icon.svg -t composable_function --multicolor-template theme.j2
# Batch convert with multicolor support
svg2compose batch icons/ output/ -t val_declaration --multicolor-template theme_template.j2
# Show SVG file details
svg2compose info icon.svg
svg-to-compose-vector
provides four built-in templates for different use cases:
Basic ImageVector.Builder code without wrapper - ideal for embedding in existing code.
Usage:
svg2compose convert icon.svg -t default
Output:
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
ImageVector.Builder(
name = "Icon",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color.Red)) {
moveTo(12f, 12f)
lineTo(18f, 12f)
// ... more path commands
}
}.build()
Creates a lazy val property - perfect for icon collections and design systems.
Usage:
svg2compose convert home_icon.svg -t val_declaration
Output:
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
val HomeIconIcon: ImageVector = ImageVector.Builder(
name = "HomeIcon",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color.Black)) {
moveTo(10f, 20f)
verticalLineTo(14f)
// ... more path commands
}
}.build()
Generates a @Composable function with modifier and tint parameters - ideal for reusable components.
Usage:
svg2compose convert search_icon.svg -t composable_function
Output:
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@Composable
fun SearchIconIcon(
modifier: Modifier = Modifier,
tint: Color = Color.Unspecified
): ImageVector {
return remember {
ImageVector.Builder(
name = "SearchIcon",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color.Black)) {
moveTo(15.5f, 14f)
horizontalLineTo(14.71f)
// ... more path commands
}
}.build()
}
}
Creates an object with a lazy ImageVector property - useful for organized icon libraries.
Usage:
svg2compose convert settings_icon.svg -t icon_object
Output:
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
object SettingsIconIcon {
val imageVector: ImageVector by lazy {
ImageVector.Builder(
name = "SettingsIcon",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color.Black)) {
moveTo(19.14f, 12.94f)
curveTo(19.18f, 12.64f, 19.2f, 12.33f, 19.2f, 12f)
// ... more path commands
}
}.build()
}
}
Create powerful custom Jinja2 templates using the complete set of available variables and filters.
Variable | Type | Description | Example Value |
---|---|---|---|
imports |
str | Formatted import statements | import androidx.compose.ui.graphics.Color |
build_code |
str | Complete ImageVector.Builder code | ImageVector.Builder(...) |
name |
NameComponents | Complete name object with all properties | See properties below |
namespace |
str | Namespace in PascalCase | Navigation |
icon |
str | Icon name in PascalCase | HomeIcon |
full_name |
str | Full hierarchical name | Navigation.HomeIcon |
icon_name |
str | Raw icon name for filters | home_icon |
The name
object provides extensive naming options, for example, if the input is navigation.home-icon.svg
:
Property | Description | Output |
---|---|---|
name.raw_name |
Original input name | navigation.home-icon |
name.name |
Clean base name | home_icon |
name.namespace_part |
Namespace portion | navigation |
name.name_part |
Name portion | home_icon |
name.full_path |
Complete path | navigation.home_icon |
name.namespace_part_pascal |
Namespace in PascalCase | Navigation |
name.name_part_pascal |
Name in PascalCase | HomeIcon |
name.full_path_pascal |
Full path in PascalCase | Navigation.HomeIcon |
name.namespace_part_camel |
Namespace in camelCase | navigation |
name.name_part_camel |
Name in camelCase | homeIcon |
name.full_path_camel |
Full path in camelCase | navigation.homeIcon |
Transform any string with built-in filters:
Filter | Description | Example Usage | Input | Output |
---|---|---|---|---|
pascal_case |
Convert to PascalCase | {{ "home-icon" | pascal_case }} |
home-icon |
HomeIcon |
camel_case |
Convert to camelCase | {{ "home-icon" | camel_case }} |
home-icon |
homeIcon |
snake_case |
Convert to snake_case | {{ "HomeIcon" | snake_case }} |
HomeIcon |
home_icon |
indent |
Add indentation | {{ build_code | indent(4, first=False) }} |
Code block | Indented code |
1. Icon Library with Documentation
{{- imports }}
/**
* {{ name.name_part_pascal }} icon from {{ name.namespace_part_pascal or "Default" }} category
* Generated from: {{ name.raw_name }}
*/
val {{ name.name_part_pascal }}Icon: ImageVector by lazy {
{{ build_code | indent(2, first=False) }}
}
Kotlin Output:
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
/**
* HomeIcon icon from Navigation category
* Generated from: navigation.home-icon
*/
val HomeIconIcon: ImageVector by lazy {
ImageVector.Builder(
name = "HomeIcon",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color.Black)) {
moveTo(10f, 20f)
verticalLineTo(14f)
horizontalLineTo(14f)
// ... path data
}
}.build()
}
2. Sealed Class Icon System
{{- imports }}
sealed class {{ name.namespace_part_pascal or "Icons" }} {
object {{ name.name_part_pascal }} : {{ name.namespace_part_pascal or "Icons" }}() {
val imageVector: ImageVector by lazy {
{{ build_code | indent(6, first=False) }}
}
}
}
Kotlin Output:
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
sealed class Ui {
object Button : Ui() {
val imageVector: ImageVector by lazy {
ImageVector.Builder(
name = "Button",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color.Black)) {
moveTo(2f, 6f)
curveTo(2f, 4.9f, 2.9f, 4f, 4f, 4f)
// ... path data
}
}.build()
}
}
}
3. Composable with Custom Parameters
{{- imports }}
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
@Composable
fun {{ name.name_part_pascal }}(
size: Dp = 24.dp,
tint: Color = Color.Unspecified,
contentDescription: String? = "{{ name.name_part | replace("_", " ") | title }}"
): ImageVector {
return remember(size, tint) {
{{ build_code | indent(4, first=False) }}
}
}
Kotlin Output:
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.Dp
@Composable
fun SearchIcon(
size: Dp = 24.dp,
tint: Color = Color.Unspecified,
contentDescription: String? = "Search Icon"
): ImageVector {
return remember(size, tint) {
ImageVector.Builder(
name = "SearchIcon",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color.Black)) {
moveTo(15.5f, 14f)
horizontalLineTo(14.71f)
// ... path data
}
}.build()
}
}
Conditional Logic:
{{- imports }}
{% if name.namespace_part %}
// {{ name.namespace_part_pascal }} Category Icons
package com.myapp.icons.{{ name.namespace_part | snake_case }}
{% endif %}
{% if name.namespace_part == "navigation" %}
@NavigationIcon
{% elif name.namespace_part == "action" %}
@ActionIcon
{% endif %}
val {{ name.name_part_pascal }}Icon: ImageVector = {{ build_code }}
Loop Through Categories:
{{- imports }}
// Generated icon path: {{ name.categories | join(" → ") }}
{% for category in name.categories[:-1] %}
// Category: {{ category | pascal_case }}
{% endfor %}
// Icon: {{ name.categories[-1] | pascal_case }}
val {{ name.full_path_pascal | replace(".", "") }}Icon: ImageVector = {{ build_code }}
Using Custom Template:
# Create your template file
svg2compose convert navigation.home.svg -t my_custom_template.j2 -o HomeIcon.kt
# The template receives all the variables and can generate any format you need
Transform SVG files with multiple colors into theme-aware @Composable functions using intelligent color mapping.
Multicolor templates enable automatic color parameterization, allowing icons to adapt to different themes:
- Smart Color Detection: Automatically extracts colors from SVG files
- Intelligent Template Selection: Uses multicolor template when SVG colors match mappings, falls back to regular template otherwise
- Works with Any Template: Combine with
default
,composable_function
,val_declaration
, or custom templates - Partial Mapping Support: Unmapped colors retain original hex values
- Transparency Support: Full ARGB support for transparent colors
Creates function parameters for theme customization:
{{- imports }}
{%- set color_mappings = {
"#FF0000": {"semantic_name": "errorColor", "replacement": "Color.Red"},
"#0000FF": {"semantic_name": "primaryColor", "replacement": "MaterialTheme.colorScheme.primary"}
} -%}
@Composable
fun {{ name.name_part_pascal }}(
{%- for color_hex, mapping in color_mappings.items() if color_hex in used_colors %}
{{ mapping.semantic_name }}: Color = {{ mapping.replacement }}{{ "," if not loop.last }}
{%- endfor %}
): ImageVector {
return {{ build_code_with_color_params }}
}
Generated Output:
@Composable
fun MyIcon(
errorColor: Color = Color.Red,
primaryColor: Color = MaterialTheme.colorScheme.primary
): ImageVector {
return ImageVector.Builder(...).apply {
path(fill = SolidColor(errorColor)) { /* red paths */ }
path(fill = SolidColor(primaryColor)) { /* blue paths */ }
}.build()
}
Directly replaces colors without creating parameters:
{{- imports }}
{%- set color_mappings = {
"#FF0000": "MaterialTheme.colorScheme.error",
"#0000FF": "MaterialTheme.colorScheme.primary"
} -%}
@Composable
fun {{ name.name_part_pascal }}(): ImageVector {
return {{ build_code_with_color_params }}
}
Generated Output:
@Composable
fun MyIcon(): ImageVector {
return ImageVector.Builder(...).apply {
path(fill = SolidColor(MaterialTheme.colorScheme.error)) { /* red paths */ }
path(fill = SolidColor(MaterialTheme.colorScheme.primary)) { /* blue paths */ }
}.build()
}
Combine both approaches in a single template:
{%- set color_mappings = {
"#FF0000": {"semantic_name": "errorColor", "replacement": "Color.Red"},
"#0000FF": "MaterialTheme.colorScheme.primary",
"#00FF00": "MaterialTheme.colorScheme.secondary"
} -%}
Transparent colors are fully supported using ARGB hex format:
{%- set color_mappings = {
"#2196F3": {"semantic_name": "primaryColor", "replacement": "MaterialTheme.colorScheme.primary"},
"#802196F3": {"semantic_name": "primaryAlpha", "replacement": "MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)"},
"#FF9800": "MaterialTheme.colorScheme.secondary"
} -%}
#802196F3
maps 50% transparent blue (0x80
= 128/255 ≈ 0.5 alpha)#CCFF9800
would map 80% transparent orange (0xCC
= 204/255 ≈ 0.8 alpha)- Mix semantic parameters with direct theme replacements for optimal flexibility
Multicolor templates have access to additional variables:
Variable | Description | Example |
---|---|---|
build_code_with_color_params |
ImageVector code with color substitution | ImageVector.Builder(...) |
used_colors |
Set of colors found in SVG | {"#FF0000", "#0000FF"} |
color_mappings |
Your color mapping configuration | {...} |
Theme-aware icon library:
# Convert with multicolor template only
svg2compose convert icon.svg --multicolor-template examples/templates/material_theme.j2
# Combine multicolor with composable function template (recommended)
svg2compose convert icon.svg -t composable_function --multicolor-template material_theme.j2
# Batch convert entire icon set with fallback template
svg2compose batch icons/ src/icons/ -t val_declaration --multicolor-template material_theme.j2
What happens:
- Match found: Uses
material_theme.j2
with color parameterization - No match: Falls back to specified template (
composable_function
orval_declaration
) - No template specified: Falls back to
default
template - Result: Consistent output format regardless of color content
Custom color scheme:
svg2compose convert brand_icon.svg -t icon_object --multicolor-template brand_colors.j2
The system intelligently chooses when to use multicolor templates:
- Color Intersection Check: When SVG colors overlap with template color mappings
- Use Multicolor Template: Apply color substitution when matches are found
- Fallback to Regular Template: When no colors match, use the specified template without color substitution
Important: Multicolor templates work with any regular template. The system behavior:
-
With both templates:
svg2compose convert icon.svg -t composable_function --multicolor-template theme.j2
- Use
theme.j2
when colors match - Fall back to
composable_function
when no matches
- Use
-
Multicolor only:
svg2compose convert icon.svg --multicolor-template theme.j2
- Use
theme.j2
when colors match - Fall back to
default
template when no matches
- Use
This ensures you always get a valid output while gaining color parameterization when applicable.
All standard Jinja2 features are available:
- Conditionals:
{% if %}
,{% elif %}
,{% else %}
- Loops:
{% for item in list %}
- Macros:
{% macro %}
for reusable code blocks - Comments:
{# This is a comment #}
- String operations:
{{ string | upper | replace("_", "-") }}
Convert multiple SVG files at once with consistent formatting:
# Convert all icons to composable functions
svg2compose batch icons/ src/main/kotlin/icons/ -t composable_function
Input directory structure:
icons/
├── home.svg
├── search.svg
└── settings.svg
Output directory structure:
src/main/kotlin/icons/
├── Home.kt
├── Search.kt
└── Settings.kt
Generated file content (Home.kt):
@Composable
fun HomeIcon(
modifier: Modifier = Modifier,
tint: Color = Color.Unspecified
): ImageVector {
return remember {
ImageVector.Builder(
name = "Home",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f,
).apply {
path(fill = SolidColor(Color.Black)) {
moveTo(10f, 20f)
verticalLineTo(14f)
horizontalLineTo(14f)
// ... more path commands
}
}.build()
}
}
svg2compose convert complex_icon.svg -t composable_function
Automatically handles gradients and transforms:
@Composable
fun ComplexIcon(): ImageVector {
return remember {
ImageVector.Builder(
name = "ComplexIcon",
defaultWidth = 48.dp,
defaultHeight = 48.dp,
viewportWidth = 48f,
viewportHeight = 48f,
).apply {
group(
name = "rotated-group",
rotate = 45f,
pivotX = 24f,
pivotY = 24f,
) {
path(
fill = Brush.linearGradient(
colorStops = arrayOf(
0f to Color.Red,
0.5f to Color.Yellow,
1f to Color.Blue,
),
start = Offset(0f, 0f),
end = Offset(48f, 48f),
),
) {
// path data...
}
}
}.build()
}
}
Feature | Support | Notes |
---|---|---|
Paths | ✅ Complete | All path commands (M, L, C, A, Z, etc.) |
Basic Shapes | ✅ Complete | rect, circle, ellipse, line, polygon, polyline |
Groups & Transforms | ✅ Complete | translate, scale, rotate, matrix decomposition |
Colors | ✅ Complete | hex, rgb, hsl, named colors with alpha support |
Gradients | ✅ Complete | Linear and radial gradients with multiple stops |
Strokes | ✅ Complete | Width, opacity, caps, joins, gradient strokes |
ClipPath | ✅ Basic | Simple clipPath support for groups |
Text | Text elements not supported by Compose ImageVector | |
Filters | Filter effects not supported by Compose ImageVector | |
Animations | Animation elements not supported (static conversion only) |
Use configuration files for consistent settings across projects:
{
"template": "composable_function",
"indent_size": 2,
"use_named_colors": true,
"optimize_output": true
}
svg2compose convert icon.svg -c config.json
git clone https://github.com/chachako/svg-to-compose-vector.git
cd svg-to-compose-vector
# Setup development environment
uv sync --dev
# Run tests
uv run pytest
# Code formatting and linting
uv run ruff check
uv run ruff format
- Python 3.13+
- Dependencies: jinja2, click (automatically installed)
MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit issues and pull requests.
This project was inspired by the Valkyrie project.