Skip to content

rozd/icon-kit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

25 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

IconKit

macOS Swift 6.2 Release codecov License

A Swift library and CLI for working with Apple .icon bundles and Android adaptive icons.

IconKit reads and writes the .icon bundle format introduced with Icon Composer β€” Apple's structured icon format containing multiple image layers (front, middle, back) at various sizes, enabling dynamic rendering effects like parallax and lighting. It also supports Android adaptive icons (XML format with PNG/WebP assets). Use it to generate icons from SF Symbols, inspect bundle structure, add environment ribbons to existing icons, manipulate layers programmatically, or validate round-trip fidelity.


✨ Features

  • πŸ”£ SF Symbol Icons β€” generate .icon bundles from any SF Symbol with configurable background, foreground color, size, and offset. Perfect for prototyping and internal tools.
  • πŸŽ€ Ribbon Overlays β€” stamp UAT / QA / Staging labels onto .icon bundles or Android adaptive icons in one command. Configurable placement, colors, font, and size.
  • πŸ€– Android Adaptive Icons β€” read and write Android adaptive icon XML format with PNG and WebP asset support. Ribbon overlays are composited onto foreground layers at each density.
  • πŸ“¦ Round-Trip Safe β€” read an .icon bundle, inspect or modify it, write it back out without data loss.
  • 🧩 Full Document Model β€” typed Swift structs for every part of the .icon format: groups, layers, fills, shadows, blend modes, specializations, and platform targeting.
  • 🎨 Appearance & Idiom Variants β€” first-class support for light/dark/tinted appearances and per-platform (iOS, macOS, watchOS, visionOS) specializations.
  • πŸ–₯️ CLI + Library β€” use the iconkit command-line tool directly, or embed the IconKit library in your own Swift code.

πŸš€ CLI Usage

Install

Homebrew

brew tap rozd/tap
brew install iconkit

Build from source

swift build -c release
# Binary is at .build/release/iconkit

Add a ribbon

Stamp an environment label onto an existing .icon bundle:

iconkit ribbon top \
  --text "UAT" \
  --input AppIcon.icon \
  --output AppIcon.uat.icon

Customize the appearance:

iconkit ribbon topLeft \
  --text "DEV" \
  --input AppIcon.icon \
  --output AppIcon.dev.icon \
  --background "#4A90D9" \
  --foreground "#FFFFFF" \
  --size 0.3 \
  --font-scale 0.5
Ribbon options
Option Default Description
<placement> β€” top, bottom, topLeft, or topRight
--text β€” Text to render on the ribbon
--size 0.24 Ribbon height as a factor of icon height (0.0–1.0)
--offset 0.0 Offset from edge as a factor of icon height
--background #B92636 Ribbon background color (hex)
--foreground #FEFAFA Text color (hex)
--font System Font family name
--font-scale 0.6 Text size as a factor of ribbon height

Add a ribbon to an Android adaptive icon

The same ribbon command works with Android adaptive icons. Pass a res/ directory or an adaptive icon XML file:

# From a res/ directory (auto-discovers XML in mipmap-anydpi-v26/)
iconkit ribbon bottom \
  --text "DEV" \
  --input app/src/main/res \
  --output app/src/debug/res

# From an XML file directly
iconkit ribbon topLeft \
  --text "QA" \
  --input res/mipmap-anydpi-v26/ic_launcher.xml \
  --output res-qa

The ribbon is composited onto every density variant of the foreground layer (mdpi through xxxhdpi). Input format is auto-detected. WebP foreground images are supported (read as WebP, written back as PNG after compositing).

Generate an icon from an SF Symbol

Create a new .icon bundle from any SF Symbol:

iconkit generate sf \
  --symbol "shippingbox.fill" \
  --background "#4A90D9" \
  --foreground "#FFFFFF" \
  --size 0.8 \
  --output AppIcon.icon
Generate options
Option Default Description
--symbol β€” SF Symbol name (e.g. shippingbox.fill)
--output β€” Path to output .icon bundle
--background #007AFF Icon background color (hex)
--foreground #FFFFFF Symbol color (hex)
--size 0.6 Symbol size as a fraction of icon space (0.0–1.0)
--offset-x 0.0 Horizontal offset as a fraction of icon width
--offset-y 0.0 Vertical offset as a fraction of icon height

Inspect a bundle

Examine the structure of an .icon bundle:

iconkit inspect AppIcon.icon
AppIcon.icon
  Fill: automatic-gradient srgb:0.69804,0.65098,0.60392,1.00000
  Platforms: circles [watchOS], squares shared
  Group 1
    Lighting: individual
    Shadow: none (opacity: 0.5)
    Layer "Fitness Art"
      Image: Fitness Art.png
      Glass: true
      Position: scale 2.0, translate (0.0, 0.0)
      Fill specializations:
        [default] solid display-p3:0.05882,0.08235,0.09804,1.00000
        [dark] solid display-p3:0.94902,0.93725,0.87843,1.00000
  Assets: 1 present, 0 missing

Use --json for machine-readable output (raw icon.json, pretty-printed):

iconkit inspect --json AppIcon.icon

Validate round-trip fidelity

Read a bundle and write it back to verify nothing is lost:

iconkit test --input AppIcon.icon --output AppIcon.copy.icon

πŸ€– GitHub Action

Stamp environment ribbons onto your app icons in CI/CD before building:

- uses: rozd/icon-kit@v1
  with:
    text: UAT
    input: App/Assets.xcassets/AppIcon.icon

The action downloads a pre-built iconkit binary from the matching GitHub Release and runs the ribbon command. Currently requires a macOS runner (Linux support for Android-only projects is planned).

Inputs

Input Required Default Description
text βœ… β€” Text to render on the ribbon
input βœ… β€” Path to .icon bundle, adaptive icon XML, or Android res/ directory
output same as input Output path β€” omit for in-place modification
placement bottom top, bottom, topLeft, or topRight
size 0.24 Ribbon height as a factor of icon height
offset 0.0 Offset from edge as a factor of icon height
background #B92636 Ribbon background color (hex)
foreground #FEFAFA Text color (hex)
font System Font family name
font-scale 0.6 Text size as a factor of ribbon height
version action ref Pin to a specific IconKit release (e.g. v1.2.3)

Example: Stamp a UAT build

jobs:
  build:
    runs-on: macos-15
    steps:
      - uses: actions/checkout@v4

      - uses: rozd/icon-kit@v1
        with:
          placement: topLeft
          text: UAT
          input: App/Assets.xcassets/AppIcon.icon
          background: '#4A90D9'

      - name: Build app
        run: xcodebuild -project App.xcodeproj -scheme App archive

Example: Android adaptive icon

- uses: rozd/icon-kit@v1
  with:
    placement: bottom
    text: DEV
    input: app/src/main/res

The ribbon is composited onto every density variant of the foreground layer.

πŸ“¦ Integration

Swift Package Manager

Add IconKit as a dependency in your Package.swift:

dependencies: [
    .package(url: "https://github.com/rozd/icon-kit", from: "0.1.0")
]

Then add the product to your target:

.target(
    name: "YourTarget",
    dependencies: [
        .product(name: "IconKit", package: "icon-kit")
    ]
)

πŸ› οΈ Library Usage

Read and inspect a bundle

import IconKit

let icon = try IconComposerDescriptorFile(contentsOf: bundleURL)

// Human-readable summary
print(icon.inspectSummary(bundleName: "AppIcon.icon"))

// Check for missing referenced assets
let warnings = icon.validateAssets()

Add a ribbon overlay

var icon = try IconComposerDescriptorFile(contentsOf: bundleURL)

let style = RibbonStyle(
    text: "UAT",
    size: 0.24,
    offset: 0.0,
    background: try parseHexColor("#B92636"),
    foreground: try parseHexColor("#FEFAFA"),
    fontScale: 0.6
)

try icon.applyRibbon(placement: .top, style: style)
try icon.write(to: outputURL)

Add a ribbon to an Android adaptive icon

var icon = try AdaptiveIconFile(contentsOf: resDirURL)

let style = RibbonStyle(
    text: "DEV",
    background: try parseHexColor("#4A90D9"),
    foreground: try parseHexColor("#FFFFFF")
)

try icon.applyRibbon(placement: .bottom, style: style)
try icon.write(to: outputResDirURL)

Generate an icon from an SF Symbol

let style = SFSymbolStyle(
    symbolName: "shippingbox.fill",
    foreground: try parseHexColor("#FFFFFF"),
    size: 0.8
)

let background = try parseHexIconColor("#4A90D9")
let icon = try IconComposerDescriptorFile.sfSymbol(
    style: style,
    background: background
)
try icon.write(to: outputURL)

Work with layers and specializations

// Access layers
for group in icon.document.groups {
    for layer in group.layers {
        print(layer.name ?? "unnamed", layer.imageName ?? "no image")
    }
}

// Resolve a specialization for dark mode on iOS
let fill = resolveSpecialization(
    base: layer.fill,
    specializations: layer.fillSpecializations ?? [],
    appearance: .dark,
    idiom: .iOS
)

βš™οΈ How It Works

An .icon bundle is a directory containing:

AppIcon.icon/
β”œβ”€β”€ icon.json          # Document descriptor (groups, layers, fills, effects)
└── Assets/
    β”œβ”€β”€ Background.svg
    β”œβ”€β”€ Foreground.png
    └── ...

IconKit models the full icon.json structure as typed Swift structs β€” IconDocument, IconGroup, IconLayer, and supporting types like IconFill, IconShadow, IconBlendMode, and Specialization<T>. Every field round-trips cleanly through Codable.

The ribbon feature works by generating a transparent PNG overlay and inserting it as the front-most layer (group index 0), with liquid glass automatically disabled to ensure opaque, true colors.

Android Adaptive Icons

An Android adaptive icon is an XML descriptor referencing foreground and background layers:

res/
β”œβ”€β”€ mipmap-anydpi-v26/
β”‚   └── ic_launcher.xml    # <adaptive-icon> descriptor
β”œβ”€β”€ mipmap-hdpi/
β”‚   β”œβ”€β”€ ic_launcher_foreground.png   # (or .webp)
β”‚   └── ic_launcher_background.png
β”œβ”€β”€ mipmap-xxhdpi/
β”‚   └── ...
└── ...

Since Android adaptive icons only support foreground + background layers (no arbitrary layer stacking), ribbons are composited directly onto the foreground PNG at each density. Both PNG and WebP inputs are supported.

About

IconKit is a Swift library and CLI tool (Swift Package) for working with Apple's `.icon` bundle format

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors