---
title: rozd/icon-kit
framework: Swift Package Catalog
role: article
path: packages/rozd/icon-kit
---

# rozd/icon-kit

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

## ✨ 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 & Vector Drawables** — read and write Android adaptive icons in both modern vector drawable (`<vector>` XML) and bitmap (PNG/WebP) formats. Foreground vectors are rendered and stamped across all densities (mdpi through xxxhdpi), with legacy launcher icon support. - 📦 **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

```bash brew tap rozd/tap brew install iconkit ```

#### Build from source

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

### Add a ribbon

Stamp an environment label onto an existing `.icon` bundle:

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

Customize the appearance:

```bash iconkit ribbon topLeft \   --text "DEV" \   --input AppIcon.icon \   --output AppIcon.dev.icon \   --background "#4A90D9" \   --foreground "#FFFFFF" \   --size 0.3 \   --font-scale 0.5 ```

<details> <summary>Ribbon options</summary>

| 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 |

</details>

### 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:

```bash # 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:

```bash iconkit generate sf \   --symbol "shippingbox.fill" \   --background "#4A90D9" \   --foreground "#FFFFFF" \   --size 0.8 \   --output AppIcon.icon ```

<details> <summary>Generate options</summary>

| 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 |

</details>

### Inspect a bundle

Examine the structure of an `.icon` bundle:

```bash 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):

```bash iconkit inspect --json AppIcon.icon ```

### Validate round-trip fidelity

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

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

## 🤖 GitHub Action

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

```yaml - 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

```yaml 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

```yaml - 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`:

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

Then add the product to your target:

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

## 🛠️ Library Usage

### Read and inspect a bundle

```swift 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

```swift 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

```swift 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

```swift 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

```swift // 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 │   └── ic_launcher_round.xml ├── drawable/ │   └── ic_launcher_foreground.xml  # Vector Drawable (<vector>) or PNG/WebP ├── values/ │   └── ic_launcher_background.xml  # Color definition (<color>) └── mipmap-xxhdpi/     ├── ic_launcher.png             # Legacy launcher icon (or .webp)     └── ic_launcher_round.png ```

IconKit supports both modern Vector Drawable foregrounds (`<vector>` XML) and density-qualified bitmap assets (`.png` and `.webp`). When a Vector Drawable is used, IconKit renders it at all standard densities (mdpi: 108px, hdpi: 162px, xhdpi: 216px, xxhdpi: 324px, xxxhdpi: 432px), composites the ribbon, and writes clean density assets compatible with Android's AAPT2 build system. Legacy launcher icons (`mipmap-*/ic_launcher.*`) are also automatically stamped.

## Package Metadata

Repository: rozd/icon-kit

Default branch: main

README: README.md
