--- name: duotone-design description: Web and App implementation guide for Duotone Design. Trigger when user wants two-color schemes, striking imagery, and Spotify-like playlist aesthetics. date_added: "2026-06-17" risk: safe source: self source_type: self --- # Duotone Design > "Striking contrast. Photography and UI stripped down to exactly two clashing or complementary colors." ## When to Use Use this sub-style when the user's request matches the aesthetic described above. This is a child reference of the `design-it` skill and is not meant to be triggered directly. ## Core Principles 1. **Two Colors Only**: The entire design is mapped to a dark color (replacing blacks/shadows) and a light color (replacing whites/highlights). 2. **Treated Imagery**: All photos MUST be processed into the duotone palette. 3. **Bold, Flat Typography**: Text is usually massive, solid, and uses one of the two colors. ## Visual DNA - **Colors**: Very high contrast pairs. Navy and Peach, Deep Purple and Neon Green, Crimson and Cream. Look at **Industrial Chic** for inspiration. - **Typography**: Heavy, condensed sans-serifs (e.g., `League Gothic`, `Oswald`). - **Imagery**: High-contrast, gritty photography works best when mapped to duotone. ## Web Implementation - Modern CSS can achieve image duotone effects without Photoshop, using `mix-blend-mode` and filters. - **CSS Example**: ```css :root { --duo-dark: #1E0045; /* Deep Purple */ --duo-light: #CCFF00; /* Neon Lime */ } body { background-color: var(--duo-dark); color: var(--duo-light); } /* CSS Duotone Image Effect */ .duotone-container { position: relative; width: 100%; height: 400px; background-color: var(--duo-light); /* Base color */ } .duotone-container img { width: 100%; height: 100%; object-fit: cover; /* Convert image to grayscale, increase contrast */ filter: grayscale(100%) contrast(1.5); /* Multiply the grayscale image against the light background */ mix-blend-mode: multiply; } .duotone-container::after { /* Overlay the dark color using screen/lighten */ content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--duo-dark); mix-blend-mode: screen; } .duotone-btn { background: var(--duo-light); color: var(--duo-dark); border: none; font-weight: 900; text-transform: uppercase; padding: 16px 32px; } ``` ## App Implementation ### SwiftUI ```swift struct DuotoneImage: View { let duoDark = Color(red: 0.12, green: 0.0, blue: 0.27) // #1E0045 let duoLight = Color(red: 0.8, green: 1.0, blue: 0.0) // #CCFF00 var body: some View { ZStack { // Background base color duoLight.ignoresSafeArea() // Image processing Image("sample_photo") .resizable() .scaledToFill() .grayscale(1.0) .contrast(1.5) .colorMultiply(duoLight) // Multiplies the light color into the grays // Dark color overlay duoDark .blendMode(.screen) // Equivalent to CSS screen blend mode .allowsHitTesting(false) } .frame(height: 400) .clipped() } } ``` - Real-time image processing is easy in SwiftUI. - Convert to `.grayscale()`, boost `.contrast()`, then use `.colorMultiply()` and `.blendMode(.screen)` layers to map the two colors exactly like CSS `mix-blend-mode`. ### Flutter ```dart class DuotoneImage extends StatelessWidget { final Color duoDark = const Color(0xFF1E0045); final Color duoLight = const Color(0xFFCCFF00); @override Widget build(BuildContext context) { return Container( height: 400, width: double.infinity, color: duoLight, child: Stack( fit: StackFit.expand, children: [ // 1. Grayscale & Contrast (using ColorFilter matrix) // 2. Light Color Multiply ColorFiltered( colorFilter: ColorFilter.mode(duoLight, BlendMode.multiply), child: ColorFiltered( // Simple grayscale matrix colorFilter: const ColorFilter.matrix([ 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0, 0, 0, 1, 0, ]), child: Image.asset('assets/sample_photo.jpg', fit: BoxFit.cover), ), ), // 3. Dark Color Screen Overlay ColorFiltered( colorFilter: ColorFilter.mode(duoDark, BlendMode.screen), child: Container(color: Colors.transparent), // Applies filter to stack below ), ], ), ); } } ``` - Flutter requires stacking `ColorFiltered` widgets. - Use a `ColorFilter.matrix` to convert the image to grayscale first. - Apply `BlendMode.multiply` with the light color, then overlay the dark color using `BlendMode.screen`. ### React Native ```jsx // Real-time CSS-like blend modes do NOT exist natively in React Native. // You must use react-native-skia or pre-process images. import { Canvas, Image, useImage, ColorMatrix } from "@shopify/react-native-skia"; const DuotoneImage = () => { const image = useImage(require('./sample_photo.jpg')); if (!image) return null; // Skia allows custom SVG/CSS style color matrices. // Building a true duotone matrix requires math mapping black to duoDark // and white to duoLight. return ( {/* Note: In production, you would construct a specific ColorMatrix to map the luminance to the two hex colors. */} ); }; ``` - **Critical Limitation**: Standard React Native `` cannot do duotone blending. - **Solution 1**: Use `@shopify/react-native-skia` to apply low-level color matrices and blend modes. - **Solution 2**: Pre-process all imagery in Photoshop/Figma before importing into the app. This is the safest and most performant route. ### Jetpack Compose ```kotlin @Composable fun DuotoneImage() { val duoDark = Color(0xFF1E0045) val duoLight = Color(0xFFCCFF00) // A ColorMatrix to map luminance to the two colors is required for true duotone. // For simplicity, we use BlendModes here to approximate the CSS multiply/screen effect. Box(modifier = Modifier .fillMaxWidth() .height(400.dp) .background(duoLight) ) { Image( painter = painterResource(id = R.drawable.sample_photo), contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.matchParentSize(), colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) // Grayscale }) ) // Multiply light color Spacer(modifier = Modifier .matchParentSize() .background(duoLight) .graphicsLayer { blendMode = BlendMode.Multiply } ) // Screen dark color Spacer(modifier = Modifier .matchParentSize() .background(duoDark) .graphicsLayer { blendMode = BlendMode.Screen } ) } } ``` - Use `ColorFilter.colorMatrix` with `setToSaturation(0f)` to make the image grayscale. - Use `Spacer` overlays with `Modifier.graphicsLayer { blendMode = BlendMode... }` to apply the dual color mapping. - Similar to Flutter, layer `Multiply` (light) and `Screen` (dark) to achieve the effect. ## Do's and Don'ts - **DO**: Ensure the dark color is dark enough to be legible when used as text against the light color. - **DON'T**: Add a third color. It instantly ruins the aesthetic. ## Limitations - This is a styling reference and does not replace environment-specific validation, accessibility testing, or expert review. - Ensure appropriate contrast ratios and responsive behaviors are verified separately.