Motivation
During the development Jetpack Compose / Compose Multiplatform, we often faced the challenge of converting icons from SVG or XML format to ImageVector. While there are existing tools available for this purpose, we found that they often fell short in terms of usability, reliability, and the quality of the generated code and in some cases, even being paid π.
To address these issues, we decided to create our own tool that would streamline the conversion process and provide a better user experience.
The primary goal of this project is to offer a fast, reliable, and user-friendly solution for converting SVG and XML icons to ImageVector format, while also allowing for customization of the generated code to meet individual project needs.
Note
This project is especially relevant now as Material Icons is no longer maintained and not recommended for use in your apps. Learn more.
Available tools:
- π IntelliJ IDEA / Android Studio plugin
- π₯οΈ CLI tool
- π Gradle plugin
- Web app (π§ under development π§)
Table of Contents
- Key features
- π IDEA plugin
- π₯οΈ CLI tool
- π Gradle plugin
- Other
Key features
Core functionality:
- Support conversion from SVG and XML
- Custom kotlinpoet generator with streamlined code formatting:
- code alignment and formatting
- remove redundant code by default (e.g.
publickeyword) - remove unused imports (e.g.
kotlin.*package) - skip default ImageVector parameters
- support generation as backing property or lazy property
- optional trailing comma and explicit mode
- customize code indent
- Ability to create your unique project icon pack (+nested packs if necessary)
- High performance (6k icons processing ~5sec)
πIDEA Plugin
Plugin features
- Two conversion modes: Simple and IconPack
- Support for Drag&Drop files/directories and pasting content from clipboard
- Easy option to add more icons into existing project icon pack
- Copy generated ImageVector to clipboard or file (depends on the mode)
- Fully customizable setting for generated icons
- Build-in ImageVector previewer for any icons without compilation β¨
- The plugin is completely built using Compose Multiplatform and Tiamat navigation library
More exclusive features under development, stay tuned π
Simple mode
Note
One-click solution to convert SVG/XML to ImageVector (requires only specifying the package).
Available quick actions:
- Rename icon
- Preview current ImageVector
- Copy generated ImageVector to clipboard
Demo:
simple_mode_demo.mp4
IconPack mode
New icon pack
Note
Facilitates creating an organized icon pack with extension properties for your pack object, previewing the list of
icons, and batch importing them to your specified directory.
Demo:
iconpack_mode_new_demo.mp4
Existing icon pack
Note
Instead of importing icon pack settings, the plugin provides a direct way to import an already created icon pack from a Kotlin file.
Important
Editing features are limited for now; you can only load an existing pack and add more nested packs.
Demo:
iconpack_mode_existing_demo.mp4
ImageVector Previewer
Embedded Previewer
We personally find it very useful to have a previewer for ImageVector (such we have for SVG or XML). Previewer available for any ImageVector formats (backing or lazy property, legacy google material icons) without compose @Preview annotation and project compilation.
Previewer actions:
- Change icon background (pixel grid, white, black)
- Zoom in, zoom out icon without loosing quality
- Draw as actual size
- Fit icon to window
Demo:
imagevector_previewer.mp4
AutoCompletion Previewer
When IDEA auto-completion popup is shown for any ImageVector property, the preview image will be displayed in the popup.
Gutter Previewer
Preview inside gutter available for any ImageVector property. By clicking on the gutter icon, the original file will be opened in the editor with embedded previewer.
Requirements
| Plugin version | Min IntelliJ IDEA / Android Studio |
|---|---|
| 0.1.0 - 0.14.0 | IntelliJ IDEA 2024.1, Android Studio Koala |
| 0.15.0 | IntelliJ IDEA 2024.2, Android Studio Ladybug |
| 1.0.0 | IntelliJ IDEA 2025.3, Android Studio Panda 1 |
Installation
-
Find plugin inside IDE:
Settings > Plugins > Marketplace > Search for "Valkyrie" > Install Plugin
-
Manually: Download the latest release or build your self and install it manually using Settings -> Plugins -> βοΈ -> Install plugin from disk...
Build plugin
Precondition: IntelliJ IDEA with installed Plugin DevKit
Run ./gradlew buildPlugin to build plugin locally. Artifact will be available in
tools/idea-plugin/build/distributions/
folder
or run plugin in IDE using: ./gradlew runIde
π₯CLI tool
CLI tools can be easily integrated into scripts and automated workflows, allowing you to convert icons from specific source with predefined settings.
Install CLI
brew install ComposeGears/repo/valkyrie
Download latest CLI tool from releases.
Unzip the downloaded archive and run the CLI tool from bin folder in the terminal
You should see this message
A simple example of how to get the latest version of the CLI tool. It can be executed on CI/CD with predefined parameters.
#!/bin/bash VERSION="cli-1.0.0" TARGET_DIR="valkyrie-cli" ASSET_NAME="tmp.zip" LATEST_CLI_RELEASE_URL=$(curl --silent "https://api.github.com/repos/ComposeGears/Valkyrie/releases/tags/$VERSION" \ | jq -r '.assets[] | select(.name | startswith("valkyrie-cli")) | .browser_download_url') curl -L -o "$ASSET_NAME" "$LATEST_CLI_RELEASE_URL" mkdir -p "$TARGET_DIR" unzip -o "$ASSET_NAME" -d "$TARGET_DIR" rm "$ASSET_NAME" cd "$TARGET_DIR/bin" || exit ./valkyrie --version
Available commands:
iconpack command
A part of the CLI tool that allows you to create an icon pack with nested packs.
Usage:
./valkyrie iconpack [<options>]
Demo:
cli_iconpack.mp4
svgxml2imagevector command
A part of the CLI tool that allows you to convert SVG/XML files to ImageVector.
Usage:
./valkyrie svgxml2imagevector [<options>]
Demo:
cli_svgxml2imagevector.mp4
changelog command
Additional command to display embedded CLI changelog
Usage:
Output example:
Build CLI
Run ./gradlew buildCLI to build minified version of CLI tool. Artifact will be available in
tools/cli/build/distributions/valkyrie-cli-*.**.*-SNAPSHOT.zip.
πGradle plugin
The Gradle plugin automates the conversion of SVG/XML files to Compose ImageVector format during the build process. It's ideal for projects that need to version control icon sources and generate type-safe Kotlin code automatically.
Common scenarios
- Team collaboration: Keep SVG/XML sources in version control and let the build system generate Kotlin code for everyone
- CI/CD pipelines: Ensure icons are always generated consistently across different environments
- Design system integration: Automatically sync icon updates from design tools without manual conversion
- Large icon libraries: Efficiently manage hundreds or thousands of icons with minimal manual intervention
Plugin configuration
1. Apply the plugin
Define in your libs.versions.toml:
[plugins] valkyrie = "io.github.composegears.valkyrie:latest-version"
Add the plugin to your build.gradle.kts:
plugins {
alias(libs.plugins.valkyrie)
}2. Configure the plugin
Full gradle plugin API specification (see practical examples below):
valkyrie {
// Required: Package name for generated icons
// Defaults to Android 'namespace' if Android Gradle Plugin is applied
packageName = "com.example.app.icons"
// Optional: Custom output directory (default: build/generated/sources/valkyrie)
outputDirectory = layout.buildDirectory.dir("generated/valkyrie")
// Optional: Resource directory name containing icon files (default: "valkyrieResources")
// Icons will be discovered in src/{sourceSet}/{resourceDirectoryName}/
// Example: src/commonMain/valkyrieResources/, src/androidMain/valkyrieResources/
resourceDirectoryName = "valkyrieResources"
// Optional: Generate during IDE sync for better developer experience (default: false)
generateAtSync = false
// Optional: Force all generated ImageVectors to have a specific autoMirror value (default: not specified)
// When set to true, all icons will have autoMirror = true
// When set to false, all icons will have autoMirror = false
// When not specified, the autoMirror value from the original icon file will be preserved
// This can be overridden at the icon pack or nested pack level
autoMirror = false
// Optional: Code style configuration for generated code
codeStyle {
// Add explicit `public` modifier to generated declarations (default: false)
useExplicitMode = false
// Number of spaces used for each level of indentation in generated code (default: 4)
indentSize = 4
}
// Optional: ImageVector generation configuration
imageVector {
// Output format for generated ImageVectors (default: BackingProperty)
outputFormat = OutputFormat.BackingProperty // or OutputFormat.LazyProperty
// Use predefined Compose colors instead of hex color codes (e.g. Color.Black instead of Color(0xFF000000)) (default: true)
useComposeColors = true
// Generate `@Preview` function for ImageVector (default: false)
generatePreview = false
// Insert a trailing comma after the last element of ImageVector.Builder block and path params (default: false)
addTrailingComma = false
}
// Optional icon pack object configuration
iconPack {
// Required: Name of the root icon pack object
name = "ValkyrieIcons"
// Required: Target source set for generated icon pack object
targetSourceSet = "commonMain"
// Optional: Generate flat package structure without subfolders (default: false)
useFlatPackage = false
// Optional: Force all ImageVectors in this icon pack to have a specific autoMirror value (default: not specified)
// When set, overrides the root level autoMirror setting
// This can be overridden at the nested pack level
autoMirror = true
// Optional: Nested icon packs configuration
nested {
// Required: Name of the nested icon pack object
name = "Outlined"
// Required: The source folder path containing icons for this nested pack, relative to the `resourceDirectoryName`.
sourceFolder = "outlined"
// Optional: Force all ImageVectors in this nested pack to have a specific autoMirror value (default: not specified)
// When set, overrides the icon pack level and root level autoMirror settings
autoMirror = false
}
// You can add more nested packs if necessary
}
}3. Organize your icons
Place your icon files in the resources directory:
src/
βββ commonMain/
βββ valkyrieResources/
βββ add.svg
βββ delete.svg
βββ ic_home.xml
The plugin automatically discovers icons from src/{sourceSet}/valkyrieResources/ in all source sets.
4. Run generation
Run the Gradle task to generate ImageVector sources:
./gradlew generateValkyrieImageVector
Gradle plugin samples
Basic conversion
Simply convert SVG/XML files to ImageVector in the specified package. For this example, we will use a multiplatform project structure.
plugins {
kotlin("multiplatform")
alias(libs.plugins.valkyrie)
}
valkyrie {
packageName = "com.example.app.icons"
}
// other codePlace icons in the valkyrieResources directory:
src/
βββ commonMain/
βββ valkyrieResources/
βββ ic_brush.xml
βββ ic_compose_color.xml
βββ ic_linear_gradient.svg
βββ ic_several_path.xml
βββ ic_transparent_fill_color.xml
Run the Gradle task:
./gradlew generateValkyrieImageVector
Observe generated code in build directory:
Use icon from your Compose code:
@Composable fun Demo() { Image( imageVector = ComposeColor, contentDescription = "Color" ) }
Icon pack configuration
For better organization and type-safe access to your icons, you can create an icon pack. For this example, we will use a multiplatform project structure.
plugins {
kotlin("multiplatform")
alias(libs.plugins.valkyrie)
}
valkyrie {
packageName = "com.example.app.icons"
iconPack {
name = "ValkyrieIcons"
targetSourceSet = "commonMain" // icon pack object will be generated in commonMain source set
}
}Place icons in the valkyrieResources directory:
src/
βββ commonMain/
βββ valkyrieResources/
βββ ic_brush.xml
βββ ic_compose_color.xml
βββ ic_linear_gradient.svg
βββ ic_several_path.xml
βββ ic_transparent_fill_color.xml
Run the Gradle task:
./gradlew generateValkyrieImageVector
Observe generated code in build directory with icon pack object and icons:
Use icon from your Compose code:
@Composable fun Demo() { Image( imageVector = ValkyrieIcons.LinearGradient, contentDescription = null ) }
Icon pack with nested packs configuration
For larger projects with multiple icon sets, you can organize icons into a structured icon pack with nested packs. This approach provides better organization and type-safe access to your icons.
For this example, we will use a multiplatform project structure.
plugins {
kotlin("multiplatform")
alias(libs.plugins.valkyrie)
}
valkyrie {
packageName = "com.example.app.icons"
iconPack {
name = "ValkyrieIcons"
targetSourceSet = "commonMain"
nested {
name = "Outlined"
sourceFolder = "outlined"
}
nested {
name = "Filled"
sourceFolder = "filled"
}
}
}Organize your icons in nested folders:
src/
βββ commonMain/
βββ valkyrieResources/
βββ outlined/
β βββ add.svg
β βββ delete.svg
β βββ settings.svg
βββ filled/
βββ home.svg
βββ user.svg
βββ search.svg
Run the Gradle task:
./gradlew generateValkyrieImageVector
Observe generated code in build directory with icon pack object and icons:
Use icons from your Compose code:
@Composable fun Demo() { Image( imageVector = ValkyrieIcons.Outlined.Add, contentDescription = "Add" ) Image( imageVector = ValkyrieIcons.Filled.Home, contentDescription = "Home" ) }
Tips and tricks
Copy generated icons into source folder instead of build folder
To keep generated icons in your source folder (e.g. for version control), you can create custom Gradle tasks to sync the generated files after the generation task.
val copyTask = tasks.register<Copy>("copyValkyrieIcons") { dependsOn(tasks.named("generateValkyrieImageVector")) from(layout.buildDirectory.dir("generated/sources/valkyrie")) into(layout.projectDirectory.dir("src")) } val cleanTask = tasks.register<Delete>("cleanValkyrieGenerated") { delete(layout.buildDirectory.dir("generated/sources/valkyrie")) } tasks.register("updateValkyrieIcons") { dependsOn(copyTask) finalizedBy(cleanTask) }
AutoMirror configuration
The autoMirror parameter controls whether icons should automatically flip horizontally when used in right-to-left (
RTL) layouts. This is particularly useful for directional icons like arrows, chevrons, or navigation elements.
Configuration hierarchy:
The plugin supports a three-level hierarchy for autoMirror configuration:
- Root level - applies to all icons across the project
- Icon pack level - overrides root level for all icons in the pack
- Nested pack level - overrides both icon pack and root level for icons in the nested pack
For example, to force enable RTL support for icons in the Navigation nested pack:
valkyrie {
packageName = "com.example.app.icons"
iconPack {
name = "ValkyrieIcons"
targetSourceSet = "commonMain"
nested {
name = "Navigation"
sourceFolder = "navigation"
autoMirror = true
}
nested {
name = "Logos"
sourceFolder = "logos"
}
}
}In this example:
- Icons in the
Navigationnested pack will haveautoMirror = true - Icons in the
Logosnested pack (and any other nested pack without an explicitautoMirrorsetting) will preserve the originalautoMirrorvalue from the source icon file
Other
Output formats
| Backing property | Lazy property |
package io.github.composegears.valkyrie.backing.outlined import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp import io.github.composegears.valkyrie.backing.BackingIcons val BackingIcons.Outlined.Add: ImageVector get() { if (_Add != null) { return _Add!! } _Add = ImageVector.Builder( name = "Outlined.Add", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f, ).apply { path(fill = SolidColor(Color(0xFF232F34))) { moveTo(19f, 13f) lineTo(13f, 13f) lineTo(13f, 19f) lineTo(11f, 19f) lineTo(11f, 13f) lineTo(5f, 13f) lineTo(5f, 11f) lineTo(11f, 11f) lineTo(11f, 5f) lineTo(13f, 5f) lineTo(13f, 11f) lineTo(19f, 11f) lineTo(19f, 13f) close() } }.build() return _Add!! } @Suppress("ObjectPropertyName") private var _Add: ImageVector? = null |
package io.github.composegears.valkyrie.lazy.outlined import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp import io.github.composegears.valkyrie.lazy.LazyIcons val LazyIcons.Outlined.Add: ImageVector by lazy(LazyThreadSafetyMode.NONE) { ImageVector.Builder( name = "Outlined.Add", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f, ).apply { path(fill = SolidColor(Color(0xFF232F34))) { moveTo(19f, 13f) lineTo(13f, 13f) lineTo(13f, 19f) lineTo(11f, 19f) lineTo(11f, 13f) lineTo(5f, 13f) lineTo(5f, 11f) lineTo(11f, 11f) lineTo(11f, 5f) lineTo(13f, 5f) lineTo(13f, 11f) lineTo(19f, 11f) lineTo(19f, 13f) close() } }.build() } |
Comparison with other solutions
Source SVG icon:
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#e8eaed"> <path d="M0 0h24v24H0V0z" fill="none"/> <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> </svg>
ImageVector output:
| Valkyrie | composables.com |
package io.github.composegears.valkyrie import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp val Add: ImageVector get() { if (_Add != null) { return _Add!! } _Add = ImageVector.Builder( name = "Add", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path(fill = SolidColor(Color(0xFFE8EAED))) { moveTo(19f, 13f) horizontalLineToRelative(-6f) verticalLineToRelative(6f) horizontalLineToRelative(-2f) verticalLineToRelative(-6f) horizontalLineTo(5f) verticalLineToRelative(-2f) horizontalLineToRelative(6f) verticalLineTo(5f) horizontalLineToRelative(2f) verticalLineToRelative(6f) horizontalLineToRelative(6f) verticalLineToRelative(2f) close() } }.build() return _Add!! } @Suppress("ObjectPropertyName") private var _Add: ImageVector? = null |
import androidx.compose.runtime.Composable import androidx.compose.foundation.Image import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.unit.dp private var _Add: ImageVector? = null public val Add: ImageVector get() { if (_Add != null) { return _Add!! } _Add = ImageVector.Builder( name = "Add", defaultWidth = 24.dp, defaultHeight = 24.dp, viewportWidth = 24f, viewportHeight = 24f ).apply { path( fill = null, fillAlpha = 1.0f, stroke = null, strokeAlpha = 1.0f, strokeLineWidth = 1.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 1.0f, pathFillType = PathFillType.NonZero ) { moveTo(0f, 0f) horizontalLineToRelative(24f) verticalLineToRelative(24f) horizontalLineTo(0f) verticalLineTo(0f) close() } path( fill = SolidColor(Color(0xFFE8EAED)), fillAlpha = 1.0f, stroke = null, strokeAlpha = 1.0f, strokeLineWidth = 1.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 1.0f, pathFillType = PathFillType.NonZero ) { moveTo(19f, 13f) horizontalLineToRelative(-6f) verticalLineToRelative(6f) horizontalLineToRelative(-2f) verticalLineToRelative(-6f) horizontalLineTo(5f) verticalLineToRelative(-2f) horizontalLineToRelative(6f) verticalLineTo(5f) horizontalLineToRelative(2f) verticalLineToRelative(6f) horizontalLineToRelative(6f) verticalLineToRelative(2f) close() } }.build() return _Add!! } |
Migration guide
v0.13.0 -> v0.14.0
CLI options --iconpack-name and --nested-packs removed in favour of --iconpack
Single pack
β ./valkyrie --iconpack-name=ValkyrieIcons
β
./valkyrie --iconpack=ValkyrieIcons
Nested packs
β ./valkyrie --iconpack-name=ValkyrieIcons --nested-packs=Colored,Filled
β
./valkyrie --iconpack=ValkyrieIcons.Colored,ValkyrieIcons.Filled
Join our community
Contributors
Thank you for your contributions and support! β€οΈ
License
Developed by ComposeGears 2024
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.










