| name | icon-system |
| description | Cross-platform icon pipeline — SVG source, Android VectorDrawable, SF Symbols, Flutter/RN delivery, sizing, and parity. Use this when adding or refactoring the icon set. |
Icon System
Instructions
Icons are part of the component library, not a loose folder of assets. One SVG source, deterministic platform outputs, tokenized sizes, and a single public API per platform.
1. One Source, Many Targets
assets/icons/
├── source/ # authoring SVG (24 × 24 grid)
│ ├── check.svg
│ ├── chevron-right.svg
│ └── ...
├── android/ # generated .xml VectorDrawables
├── ios/ # generated .xcassets (+ fallback for non-SF-Symbols)
├── flutter/ # generated IconData (from a custom font) or SVG widgets
└── rn/ # generated TypeScript components (react-native-svg)
2. Authoring Rules
- 24 × 24 grid, 1.5–2pt strokes, rounded line caps/joins by default.
- Monochrome only. Color comes from the
tintColor / color prop driven by tokens.
- No hard-coded fill; use
currentColor (fill="currentColor" / stroke="currentColor").
- Export with
viewBox="0 0 24 24"; no width/height attributes.
- One visual per file; do not merge states into a single SVG.
3. Android / Compose
Convert SVG → VectorDrawable with Android Studio's importer or vd-tool, then expose via a generated Icons object.
object DsIcons {
val Check: ImageVector = Icons.Filled.Check
val ChevronRight: ImageVector = vectorResource(R.drawable.ic_chevron_right)
}
@Composable
fun DsIcon(icon: ImageVector, size: Dp = 24.dp, tint: Color = LocalContentColor.current) {
Icon(imageVector = icon, contentDescription = null, tint = tint, modifier = Modifier.size(size))
}
4. iOS / SwiftUI
Prefer SF Symbols for platform idiom when an exact-match symbol exists. For brand-specific icons, ship a multicolor-capable asset catalog.
public enum DSIcon: String {
case check = "checkmark"
case chevronRight = "chevron.right"
case brandLogo = "ds.brand.logo"
}
public struct DSIconView: View {
let icon: DSIcon
let size: CGFloat
public var body: some View {
if icon.rawValue.contains(".") {
Image(systemName: icon.rawValue)
.font(.system(size: size, weight: .regular))
} else {
Image(icon.rawValue).resizable().frame(width: size, height: size)
}
}
}
Rule: never ship a bespoke checkmark on iOS when checkmark exists in SF Symbols; use the system glyph so it inherits Dynamic Type.
5. Flutter
Two acceptable approaches:
- Icon font (most efficient): generate a custom font from SVGs via FontForge / IcoMoon, declare
IconData.
- Vector widgets via
flutter_svg: simpler, zero font tooling, slightly heavier runtime cost.
class DsIcons {
static const IconData check = IconData(0xe001, fontFamily: 'DsIcons');
static const IconData chevronRight = IconData(0xe002, fontFamily: 'DsIcons');
}
class DsIcon extends StatelessWidget {
const DsIcon(this.data, {super.key, this.size = 24, this.color});
final IconData data; final double size; final Color? color;
@override
Widget build(BuildContext context) => Icon(data,
size: size, color: color ?? IconTheme.of(context).color);
}
6. React Native
Generate one *.tsx per icon with react-native-svg. A bundler treeshakes unused icons.
export const IconCheck = (props: SvgProps) => (
<Svg viewBox="0 0 24 24" {...props}>
<Path d="M20 6L9 17l-5-5" stroke={props.color ?? 'currentColor'} strokeWidth={2}
strokeLinecap="round" strokeLinejoin="round" fill="none" />
</Svg>
);
export const Icon = ({ name, size = 24, color }: IconProps) => {
const C = REGISTRY[name];
return <C width={size} height={size} color={color} />;
};
7. Sizing via Tokens
{
"size": {
"icon": {
"sm": { "$value": "16px", "$type": "dimension" },
"md": { "$value": "20px", "$type": "dimension" },
"lg": { "$value": "24px", "$type": "dimension" },
"xl": { "$value": "32px", "$type": "dimension" }
}
}
}
Components accept a size variant and map to the token — not arbitrary px/dp.
8. Accessibility
- Decorative icons: explicit
null / empty content description; never auto-generate.
- Icon-only buttons: provide an accessibility label that describes the action, not the glyph ("Close", not "X").
- Minimum hit target: 44×44 iOS / 48×48 Android, regardless of icon size — pad the tap area.
9. Parity Strategy
Maintain a canonical registry (icons.json) listing every icon, its semantic name, and its per-platform mapping. A CI check fails if a name exists without a mapping on every platform.
{
"check": { "android": "ic_check", "ios": "checkmark", "flutter": 57345, "rn": "IconCheck" },
"chevron-right": { "android": "ic_chevron_right", "ios": "chevron.right", "flutter": 57346, "rn": "IconChevronRight" }
}
10. Anti-Patterns
- PNG icons in a modern mobile app (except raster app icons).
- Re-drawing SF Symbols with bespoke SVGs.
- Baked-in color inside icon source.
- Icon padding inside the SVG itself (breaks hit-testing math).
- Shipping every icon of the set in the binary; tree-shake or lazy-load rarely-used ones.
Checklist