add-material-icon
SkillDev toolsFetch a Material Icon SVG and convert it to a Compose ImageVector in the aktual-core:icons module. Use when the user wants to add a new Material icon.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the add-material-icon skill
What this skill tells your AI
The instructions your AI receives, as published by jonapoul/aktual in .claude/skills/add-material-icon/SKILL.md and read by ahel’s review.
Add a Material Design icon to the aktual-core/icons module by fetching its SVG source and converting it to a Kotlin ImageVector.
Do NOT ask clarifying questions — just execute.
Arguments
$ARGUMENTSshould contain the icon name in either snake_case or PascalCase (e.g.,calendar_todayorCalendarToday)- If
--classicis included, fetch from classic Material Icons (24x24) instead of Material Symbols - If
--materialis included, useMaterialIconsas the receiver instead ofAktualIcons
Step-by-Step Process
1. Determine source and fetch SVG
Material Symbols (default, 960x960):
Fetch the filled variant SVG directly from the Google Fonts CDN:
https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/{snake_case_name}/fill1/24px.svg
The fill1 path segment selects the filled variant. The response is a plain SVG file (not base64).
Classic Material Icons (with --classic, 24x24):
The source repo is: https://android.googlesource.com/platform/frameworks/support/+/1de65587b7e999a38df120bd8827c3594974864d/compose/material/material/icons/generator/raw-icons/filled/
Convert the PascalCase icon name to snake_case for the filename (e.g., AccountCircle → account_circle).
Fetch the SVG using WebFetch:
https://android.googlesource.com/platform/frameworks/support/+/1de65587b7e999a38df120bd8827c3594974864d/compose/material/material/icons/generator/raw-icons/filled/{snake_case_name}.xml?format=TEXT
The response is base64 encoded. Decode it to get the Android Vector Drawable XML.
2. Parse the SVG/Vector Drawable
From the Android Vector Drawable XML (classic), extract:
android:viewportWidthandandroid:viewportHeight(typically 24)android:pathDatafrom each<path>element
From an SVG (symbols), extract:
- The
viewBoxattribute (typically0 -960 960 960) - The
dattribute from each<path>element
3. Convert path data to Kotlin
Convert SVG/Android path commands to Kotlin PathBuilder calls:
| SVG Command | Kotlin Call |
|---|---|
M x,y | moveTo(xf, yf) |
m dx,dy | moveToRelative(dxf, dyf) |
L x,y | lineTo(xf, yf) |
l dx,dy | lineToRelative(dxf, dyf) |
H x | horizontalLineTo(xf) |
h dx | horizontalLineToRelative(dxf) |
V y | verticalLineTo(yf) |
v dy | verticalLineToRelative(dyf) |
C x1,y1 x2,y2 x,y | curveTo(x1f, y1f, x2f, y2f, xf, yf) |
c dx1,dy1 dx2,dy2 dx,dy | curveToRelative(dx1f, dy1f, dx2f, dy2f, dxf, dyf) |
S x2,y2 x,y | reflectiveCurveTo(x2f, y2f, xf, yf) |
s dx2,dy2 dx,dy | reflectiveCurveToRelative(dx2f, dy2f, dxf, dyf) |
Q x1,y1 x,y | quadTo(x1f, y1f, xf, yf) |
q dx1,dy1 dx,dy | quadToRelative(dx1f, dy1f, dxf, dyf) |
T x,y | reflectiveQuadTo(xf, yf) |
t dx,dy | reflectiveQuadToRelative(dxf, dyf) |
A rx,ry,rot,largeArc,sweep,x,y | arcTo(rxf, ryf, rotf, largeArc(bool), sweep(bool), xf, yf) |
a rx,ry,rot,largeArc,sweep,dx,dy | arcToRelative(rxf, ryf, rotf, largeArc(bool), sweep(bool), dxf, dyf) |
Z or z | close() |
Important for Material Symbols (960x960 viewport):
- The viewBox is
0 -960 960 960, meaning y ranges from -960 (top) to 0 (bottom) - For absolute y coordinates:
y_kotlin = y_svg + 960 - Relative offsets stay unchanged
- Use
materialIcon(name = "...", viewportSize = 960f)
Number formatting:
- All coordinate values must end with
f(Kotlin float literal) - Use the exact numeric values from the SVG (don't round)
- Comma-separated values in SVG should become separate float arguments
4. Create the Kotlin file
Create the file at:
aktual-core/icons/src/commonMain/kotlin/aktual/core/icons/{IconName}.kt
Use this template for classic 24x24 icons:
@file:Suppress("UnusedReceiverParameter")
package aktual.core.icons
import aktual.core.icons.internal.materialIcon
import aktual.core.icons.internal.materialPath
import androidx.compose.ui.graphics.vector.ImageVector
val MaterialIcons.{IconName}: ImageVector by lazy {
materialIcon(name = "{IconName}") {
materialPath {
// converted path commands here
}
}
}
For Material Symbols (960x960) icons:
@file:Suppress("UnusedReceiverParameter")
package aktual.core.icons
import aktual.core.icons.internal.materialIcon
import aktual.core.icons.internal.materialPath
import androidx.compose.ui.graphics.vector.ImageVector
val MaterialIcons.{IconName}: ImageVector by lazy {
materialIcon(name = "{IconName}", viewportSize = 960f) {
materialPath {
// converted path commands here (with y + 960 adjustment for absolute coords)
}
}
}
If the vector drawable has multiple <path> elements, use multiple materialPath { } blocks.
If any path has android:fillType="evenOdd", pass pathFillType = PathFillType.EvenOdd to materialPath and add the import import androidx.compose.ui.graphics.PathFillType.
5. Update IconPreviews.kt
Read aktual-core/icons/src/commonMain/kotlin/aktual/core/icons/IconPreviews.kt.
Add the new icon name to the materialIcons list in alphabetical order.
6. Verify
Run ./gradlew :aktual-core:icons:compileAll to verify the icon compiles correctly.
Important Notes
- Check that a file with this icon name doesn't already exist before creating it
- The icon name in the file must match the PascalCase name used as the extension property name
- Always use
by lazyfor the property delegate - Always include
@file:Suppress("UnusedReceiverParameter")at the top - If the fetched XML contains
android:autoMirrored="true", passautoMirror = truetomaterialIcon()
Signals
- GitHub stars
- 34
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
add-material-icon- Source
- github.com/jonapoul/aktual