SuperAPI AI Directory

icons-app-icon-and-splash

Design & creation

View repository

Updated 10-18 18:02

About this skill

Icons, app icon and splash drawn in code

Makes every icon and every piece of brand art in an Expo app come from code: UI icons are layers on a 24-unit grid flattened into one Skia path each and shown by <Icon> as tinted native images; one brand mark (layer data in theme colour roles) draws the in-app tile, the iOS light, dark and tinted icons, the Android adaptive and themed layers, the splash and the Play icon. Two checkers prove the icon set, the source bans, the rendered PNGs against each platform's rules, the config wiring and the mark.

Rules that must hold

  1. Every UI icon is a named path in the app's icon set, drawn by <Icon>. No icon font or vector-icon package, no emoji, no image file or image import in app source (real photos and a deliberately chosen package go into contracts/app-art.json allow-lists). react-native-svg is fine for charts and illustrations; a UI icon drawn with it instead of Icon is a review finding (no import scan can tell the two apart). Why: one rasterized path per icon laid out 4.8x faster than a canvas per icon, the design owns every glyph, and emoji differ per OS and are read aloud by name. (no-icon-library, no-emoji, no-image-file)
  2. src/ui/icons/icon-paths.ts is generated from design/icon-layers.json by scripts/art/build-icon-paths.ts, never edited by hand. It carries its // GENERATED by line, holds exactly the layers' icons and directional list, and every path stays on the 24 grid and in step with its layers; --check runs in the verify gate. Why: the layers are the design source, and a hand-edited path drifts silently. (icon-layers-missing, icon-paths-missing, icon-layers-invalid, generated-header, icon-module-load, icon-set, icon-path-syntax, icon-path-grid, icon-path-stale)
  3. Icon is the only renderer. It rasterizes once per name and pixel size, tints with a theme colour, is hidden from VoiceOver and TalkBack (the control around it carries the label), and is the only place raw Image and the right-to-left flip live. Which icons flip is rtl-and-direction's rule (directional-icons). Why: colour changes never re-rasterize, and no call site can forget a label or a flip.
  4. A glyph with two colours is two stacked icons (star-fill under star-edge), each tinted from the theme; three or more colours make it a picture. Why: an icon is a one-colour mask.
  5. One brand mark, as data, draws every picture of the brand. BRAND_MARK in src/ui/icons/brand-mark.ts lives on a 48 grid with 2 units clear at the edges (strokes included), at most 8 layers, and theme colour roles (never hex); BrandTile, every app icon, the splash and the store icon draw it. Why: a second drawing drifts on the first redesign, and roles make the mark follow dark mode and the palette. (brand-mark-missing, brand-mark-shape)
  6. Multi-colour art is one Skia canvas built from shared, types-only draw code, never one per list item. drawMark takes the Skia API as a parameter, so the app, the CanvasKit goldens and the Node scripts run the same code. Why: each canvas costs about 0.18 MB and 2.8 ms to mount, and a Skia value import breaks the Node scripts.
  7. The app icon, splash and store icon come from scripts/art/render-app-art.ts and are committed. iOS: 1024 px, light opaque and full bleed, dark transparent, tinted opaque grayscale, the mark in the central 62 %. Android: the foreground and the themed monochrome layer on transparency inside the 66 dp safe circle, the background layer opaque. Splash: the mark on transparency; Android 12+ shows the image at imageWidth dp (at most 288) on a 288 dp icon masked to a 192 dp circle, so the mark stays within 96 / imageWidth of the image from its centre (0.48 at the default 200). Play: 512 px, opaque, at most 1024 KB. --check runs in the verify gate. Why: stores and launchers reject or crop art that breaks these, and a re-render must not change bytes. Hand-made art follows the same platform rules (checked by eye). (art-missing, art-format, art-light-opaque, art-dark-clear, art-tinted-gray, art-safe-area, art-android-foreground, art-android-background, art-android-monochrome, art-splash-clear, art-splash-width, art-store-opaque)
  8. The app config names every drawn image, and expo-splash-screen is installed before its plugin entry exists. Production iOS uses iosAppearanceIcon(), every variant's Android layers live in assets/icons/<variant>/ (app-variants-and-environment badges the testing copies), and the splash is splashPlugin() on the palette's grounds. The checker reads the config as text; npx expo config --type public per variant is the proof. Why: an unnamed file never ships, and a plugin entry without its package stops npx expo config. (art-unwired)
  9. Look at every generated image before committing: the icon contact sheet, and each app image at 1024 and 256 px. A new icon or mark is a design decision shown to the reviewer. Why: a valid PNG can still be blank, clipped, filled solid or off centre.
  10. Repeated art in games and boards is pre-rendered once per key (palette, scheme, colour-blind flag, cell size, pixel ratio) into a sprite sheet, never redrawn per frame. Why: per-frame drawing of identical art is the most common Skia jank.

Workflow

  1. Read references/icon-system.md: the decision, the layer model, the build pipeline, Icon, tests. An app that cannot take Skia reads "If the app does not use Skia".
  2. Set up once. Install @shopify/react-native-skia (2.6.2) and npx expo install expo-splash-screen if missing. Copy from templates/, each to the path its first line names (JSON files to their path under templates/): design/icon-layers.json, contracts/app-art.json and its schema, scripts/art/ (build-icon-paths, render-icon-sheet, render-app-art, app-art-config, app-art-contract, art-cli, load-headless-skia and their tests) and src/ui/icons/ (icon-paths, icon-raster, icon, brand-mark-art, brand-mark, brand-tile, picture, mark-paths and their tests). They import the canonical theme (@/theme/*), useDirection (@/i18n/direction-context) and @test/render-with-providers, which design-system-structure, rtl-and-direction and unit-and-component-tests create. Nothing to add to jest.setup.ts: its central Skia mock already covers the rasterizer.
  3. Using an icon. Pick the name in references/icon-catalogue.md (or the app's own set), the size from the app's tokens, the colour from the theme; the surrounding button or row carries the label. Slots: icon={(look) => <Icon name="plus" {...look} />}.
  4. Adding or changing an icon. Follow icon-system.md, "Adding or changing an icon": layers into design/icon-layers.json, node scripts/art/build-icon-paths.ts, then node scripts/art/render-icon-sheet.ts and look at reports/art/icon-sheet.png.
  5. The brand mark and pictures. Read references/brand-mark-and-pictures.md; replace the demo layers of brand-mark.ts with the app's mark; draw empty states with Picture and layer data in the feature folder.
  6. App icon, splash, store art. Read references/app-icon-and-splash.md and keep its order: dependency, node scripts/art/render-app-art.ts, look at all images at 1024 and 256 px, let app-variants-and-environment redraw the badged development and preview icons, wire iosAppearanceIcon() and splashPlugin() into app.config.ts, then npx expo config --type public per variant. Commit the images with the change that drew them. Uploading store art is outward-facing: stop and ask the developer.
  7. Skia questions (headless loading, Jest projects, text): references/skia-notes.md.
  8. Run the checks from the app root and loop until all pass: npx tsc --noEmit for both programs, npx eslint <changed paths> --max-warnings 0, npx jest src/ui/icons scripts/art --ci, node scripts/art/build-icon-paths.ts --check, node scripts/art/render-app-art.ts --check, then the two checkers below. When multi-package, run each in every app folder. Once per app, put both --check runs in the verify gate: add "art:check": "node scripts/art/build-icon-paths.ts --check && node scripts/art/render-app-art.ts --check" to package.json scripts and append "art:check" to verifyExtraSteps in quality-gates.json (quality-gates' rule; a gated file, so the commit carries the Gate-Change trailer: git-commits-and-reporting).
  9. Report what was drawn or changed with the 256 px images and the contact sheet, and say plainly that the reviewer should look at any new icon or mark.

Definition of done

  • build-icon-paths.ts --check and render-app-art.ts --check exit 0; both are in the app's verify gate (quality-gates).
  • The contact sheet and every app image were looked at (1024 and 256 px); new art was shown.
  • No icon font, vector-icon package, emoji, image file or image import in app source beyond the contract's allow-lists, and no UI icon drawn with react-native-svg.
  • npx expo config --type public shows the drawn icons per variant and the splash on the palette grounds; expo-splash-screen is a dependency.
  • tsc, ESLint (--max-warnings 0) and the unit and golden Jest projects pass for the touched code.
  • node ${CLAUDE_SKILL_DIR}/scripts/check-icons.mjs . prints RESULT: PASS (before the app has an icon set it prints no icon set yet ... and checks only the source bans)
  • node ${CLAUDE_SKILL_DIR}/scripts/check-app-art.mjs . prints RESULT: PASS (expected only while adopting: SKIP src/theme/theme-types.ts [brand-mark-shape] not yet created (design-system-structure creates it); NOT APPLICABLE until the renderer exists or contracts/app-art.json declares rendered art: a contract holding only icons allow-lists does not adopt the pipeline)

Anti-patterns

  • Editing a path in icon-paths.ts to fix an icon. The next generation overwrites it and the checker flags it as stale; change the layers and regenerate.
  • An emoji or a PNG "just for now". It ships; use an Icon or a Picture.
  • @expo/vector-icons because the template had it. Two icon systems, glyphs the design does not own; draw the icon into the set.
  • A UI icon as a react-native-svg <Path> because the screen already draws a chart with it. It bypasses the set, the tint, the RTL flip and the hidden-from-screen-readers default; use Icon.
  • A Skia canvas per row or tile. Measured 4 to 5 times slower to lay out; icons in lists are Icon.
  • Flipping the mark, a play icon or a clock in RTL, or flipping an icon at the call site. Only icons that point along the reading direction flip, inside Icon.
  • Committing app art without looking at it, or uploading store art without asking.
  • Importing Skia values in shared draw code. The Node scripts then load React Native and fail; import types and take the API as a parameter.
  • An app icon made from a screenshot, or a hex colour in the mark. Render it from BRAND_MARK and the palette so the icon, splash and in-app tile stay one design in both schemes.
  • A splash or Android foreground drawn edge to edge. Android 12 and the launcher masks cut it to a circle; keep the mark inside the safe circles.
  • Hand-made dark and tinted icons next to the generated light one. They drift; render all three, or keep an Icon Composer folder and list dark and tinted as optional.

Files in this skill

File What it is Read/run when
references/icon-system.md Why rasterized paths, the layer model, build pipeline, Icon, sizes, multi-colour glyphs, mirroring, tests, adding an icon, apps without Skia Workflow step 1
references/icon-catalogue.md The 19 shipped icons: names, use, flips, layers Picking or adding an icon
references/brand-mark-and-pictures.md The mark's grid, layers and colour roles, designing a mark, BrandTile, Picture, shared draw code, canvas budget, sprites Workflow step 5
references/app-icon-and-splash.md Outputs, the contract, iOS, Android, splash and store rules, the renderer, Expo config and variants, Icon Composer, review Workflow step 6
references/skia-notes.md Where Skia runs, headless loading, APIs, declarative drawing, Jest projects, text Any Skia question
templates/design/icon-layers.json The icon source layers (19 neutral icons, the directional list) Workflow steps 2 and 4
templates/contracts/ app-art.json and its schema: where each image lives, optional images, glyph area, check-icons' allow-lists (an app that needs only the allow-lists keeps just the icons key) Workflow steps 2 and 6
templates/scripts/art/ build-icon-paths.ts, render-icon-sheet.ts, render-app-art.ts, app-art-config.ts (iosAppearanceIcon, splashPlugin), app-art-contract.ts, art-cli.ts (--help, bad-flag errors, exit codes), load-headless-skia.ts, and their tests Workflow steps 2, 4 and 6
templates/src/ui/icons/ icon-paths.ts (generated), icon-raster.ts, icon.tsx, brand-mark-art.ts, brand-mark.ts (demo mark), brand-tile.tsx, picture.tsx, mark-paths.tsx, with unit and golden tests Workflow steps 2, 3 and 5
assets/shared.json The shared files this skill copies in (check-lib, app-modules, the canonical theme types for the fixtures) When adding a shared file
scripts/check-icons.mjs Checker: icon set vs layers (names, directional list, syntax, grid, coverage), generated header, and the icon-library, emoji and image bans in app source Workflow step 8
scripts/check-app-art.mjs Checker: the rendered PNGs per platform rule, the splash width, the config wiring and dependency, the brand mark Workflow step 8
scripts/lib/ art-contract.mjs (reads contracts/app-art.json), svg-path.mjs (SVG path reader), icon-coverage.mjs (a path vs its layers, without Skia), app-modules.mjs (synced: imports the app's plain TypeScript) Never directly
scripts/check-lib.mjs Shared script helper, synced from the library (do not edit here) Never by hand
scripts/selftest.mjs Proves both checkers on the templates and planted bugs After changing a checker
tests/ bases/app/ (the fixture app) and fixtures/ (good, pass, bad and error cases; images generated at run time) When adding a rule

Related skills

  • design-system-structure - the palette and colour roles the icons and the mark use.
  • ui-component-library - the controls that take icons through slots.
  • rtl-and-direction - which icons flip (directional-icons).
  • app-variants-and-environment - per-variant icon folders and the badged testing icons.
  • skia-canvas-rendering - canvases, boards, text on canvases.
  • accessibility-as-code - labels on the controls around icons; decorative canvases.
  • unit-and-component-tests - the central Skia mock; golden-and-snapshot-tests - the golden project.
  • performance-budgets - how many canvases a screen may hold.
  • media-images-and-files - real photos and user images.
  • eas-build-and-submit, ios-release-testflight, android-release-play - the builds and store listings that carry the art.