Generate Neumorphic UI Components with Soft Shadows
Design-style skill for Neumorphism (Soft UI): elements extruded from a same-color surface via matched dual light/dark shadows.
Maintainer of this project? Claim this page to edit the listing.
13.6.1Add to Favorites
Why it matters
Design and implement modern neumorphic (soft UI) interfaces with proper depth, shadows, and tactile visual elements that create the illusion of extruded shapes emerging from the background.
Outcomes
What it gets done
Generate CSS for soft inset and outset shadows that create depth
Create component code with proper light source positioning and color theory
Build accessible neumorphic controls that maintain usability standards
Design cohesive color palettes and spacing for soft UI aesthetics
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/ag-neumorphism | bash Overview
Neumorphism (Soft UI)
A design-style skill for Neumorphism (Soft UI) - elements extruded from a same-color surface via matched light and dark dual shadows, no borders - with matching Web CSS and SwiftUI implementations. Use it for calm wellness apps, smart-home controls, or minimal utility panels where elements should feel physically extruded from or pressed into one surface, not floating on a contrasting background.
What it does
Neumorphism (Soft UI) is a design-style child skill ('elements extruded from the background material itself, shaped by a singular, persistent light source'), part of the design-it family and not meant to be triggered directly. Three core principles: Unified Surface Color (the background and every element MUST share the exact same base color - no exceptions), Dual Shadows (elements are shaped entirely by two shadows - a light highlight on the side facing the light source, a dark shadow on the opposite side), and No Borders (the shape is defined purely by shadow, never an actual border line).
When to use - and when NOT to
Use it when the user's request matches this soft, extruded-material aesthetic - calm wellness apps, smart-home controls, or minimal utility panels that want elements to feel physically pressed into or raised out of the same surface. As a design sub-style, it's selected by the parent design-it skill's routing rather than invoked on its own.
Inputs and outputs
Visual DNA works best with mid-tone neutrals - Desert Mirage, Earth-Grounded Elegance, or Sophisticated Neutral - explicitly avoiding pure white or pure black, since the light and dark shadow variants won't read against either extreme. Typography is soft, rounded sans-serifs (Nunito, Quicksand), and shapes favor pills and rounded rectangles since sharp corners break the illusion of extruded material. The entire effect lives in box-shadow: a raised element gets a light highlight shadow on one corner and a darker shadow on the opposite corner, both derived from the exact same base background color at different lightness - and a pressed/active state simply inverts both shadows to inset, making the element look sunken into the surface instead of raised out of it.
.neu-element {
background-color: var(--base-color);
box-shadow: 9px 9px 18px var(--shadow), -9px -9px 18px var(--highlight);
}
.neu-pressed {
box-shadow: inset 9px 9px 18px var(--shadow), inset -9px -9px 18px var(--highlight);
}
Integrations
SwiftUI reproduces the dual-shadow technique with two separate .shadow() modifiers on the same view - a white-tinted highlight offset toward the light source and a black-tinted shadow offset away from it - using the identical base color for the card background as the surrounding surface, with a separate pressed-button variant tracking press state to swap between the raised and inset shadow pairs.
Who it's for
Designers and developers building a calm, tactile interface - wellness apps, smart-home controls, minimal utility panels - where elements should feel physically extruded from or pressed into a single-color surface rather than floating on a contrasting background.
Source README
Neumorphism (Soft UI)
"Elements extruded from the background material itself, shaped by a singular, persistent light source."
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
- Unified Surface Color: The background and the elements MUST share the exact same base color.
- Dual Shadows: Elements are shaped by two shadows: a light shadow (highlight) on the side facing the light source, and a dark shadow on the opposite side.
- No Borders: The shape is entirely defined by the shadows.
Visual DNA
- Colors: Works best with mid-tone neutrals. Desert Mirage, Earth-Grounded Elegance, or Sophisticated Neutral are perfect. Avoid pure white or pure black (shadows/highlights won't show).
- Typography: Soft, rounded sans-serifs (e.g.,
Nunito,Quicksand). - Shapes: Pill shapes, rounded rectangles. Sharp corners break the illusion of extruded material.
Web Implementation
- The magic is entirely in
box-shadowmanipulating light and dark variants of the base color. - CSS Example:
:root {
--base-color: #E6E2DD; /* From Sophisticated Neutral */
--highlight: #ffffff;
--shadow: #c4c0bc;
}
body {
background-color: var(--base-color);
}
.neu-element {
background-color: var(--base-color);
border-radius: 20px;
/* Top-left highlight, Bottom-right shadow */
box-shadow: 9px 9px 18px var(--shadow),
-9px -9px 18px var(--highlight);
padding: 32px;
}
.neu-pressed {
/* Inset shadows for pressed/active state */
border-radius: 20px;
background: var(--base-color);
box-shadow: inset 9px 9px 18px var(--shadow),
inset -9px -9px 18px var(--highlight);
}
App Implementation
SwiftUI
struct NeuCard: View {
let baseColor = Color(red: 0.90, green: 0.89, blue: 0.87) // #E6E2DD
var body: some View {
VStack(spacing: 24) {
Text("Neumorphic Card")
.font(.system(size: 20, weight: .semibold, design: .rounded))
Text("Extruded from the surface itself.")
.font(.system(size: 15, design: .rounded))
.foregroundColor(.secondary)
}
.padding(32)
.background(baseColor)
.cornerRadius(20)
// Light shadow (top-left)
.shadow(color: Color.white.opacity(0.7), radius: 10, x: -8, y: -8)
// Dark shadow (bottom-right)
.shadow(color: Color.black.opacity(0.15), radius: 10, x: 8, y: 8)
}
}
// Pressed / inset neumorphic button
struct NeuButton: View {
@State private var isPressed = false
let baseColor = Color(red: 0.90, green: 0.89, blue: 0.87)
var body: some View {
Button(action: {}) {
Text("Press Me")
.font(.system(size: 16, weight: .semibold, design: .rounded))
.foregroundColor(.primary)
.padding(.horizontal, 32)
.padding(.vertical, 16)
}
.background(
Group {
if isPressed {
// Inset effect using inner shadow (ZStack trick)
RoundedRectangle(cornerRadius: 16)
.fill(baseColor)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(baseColor, lineWidth: 4)
.shadow(color: Color.black.opacity(0.2), radius: 4, x: 4, y: 4)
.clipShape(RoundedRectangle(cornerRadius: 16))
)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(baseColor, lineWidth: 4)
.shadow(color: Color.white.opacity(0.7), radius: 4, x: -4, y: -4)
.clipShape(RoundedRectangle(cornerRadius: 16))
)
} else {
RoundedRectangle(cornerRadius: 16)
.fill(baseColor)
.shadow(color: Color.white.opacity(0.7), radius: 10, x: -8, y: -8)
.shadow(color: Color.black.opacity(0.15), radius: 10, x: 8, y: 8)
}
}
)
.buttonStyle(.plain)
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged { _ in isPressed = true }
.onEnded { _ in isPressed = false }
)
}
}
- The key trick: two
.shadow()modifiers - one white (top-left), one dark (bottom-right). - Inner shadow (pressed state) requires a ZStack/overlay hack since SwiftUI doesn't have native
insetshadows. Clip stroked shapes to simulate. - The view's background color MUST match its parent's background exactly.
Flutter
class NeuCard extends StatelessWidget {
final Color baseColor = const Color(0xFFE6E2DD);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: baseColor,
borderRadius: BorderRadius.circular(20),
boxShadow: [
// Dark shadow (bottom-right)
BoxShadow(
color: Colors.black.withOpacity(0.15),
offset: const Offset(8, 8),
blurRadius: 16,
),
// Light shadow (top-left)
BoxShadow(
color: Colors.white.withOpacity(0.7),
offset: const Offset(-8, -8),
blurRadius: 16,
),
],
),
child: Column(
children: [
const Text('Neumorphic Card',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600)),
const SizedBox(height: 16),
Text('Extruded from the surface itself.',
style: TextStyle(fontSize: 15, color: Colors.black54)),
],
),
);
}
}
- Inner shadows (for pressed state) are NOT natively supported in Flutter's
BoxShadow. - Use the
flutter_inset_box_shadowpackage OR fake it with a layeredStack: place aContainerwith a dark gradient overlay on top and a light gradient below. - Set the
Scaffoldbackground to the SAMEbaseColorso elements look extruded.
React Native
const NeuCard = () => (
<View style={{
padding: 32,
backgroundColor: '#E6E2DD',
borderRadius: 20,
// Light shadow (top-left) — iOS only supports one shadow
shadowColor: '#FFFFFF',
shadowOffset: { width: -8, height: -8 },
shadowOpacity: 0.7,
shadowRadius: 10,
// Android — use elevation for basic shadow
elevation: 8,
}}>
<Text style={{ fontSize: 20, fontWeight: '600' }}>Neumorphic Card</Text>
<Text style={{ fontSize: 15, color: '#888', marginTop: 16 }}>
Extruded from the surface itself.
</Text>
</View>
);
- Major limitation: React Native only supports ONE shadow per view. True neumorphism requires TWO opposing shadows.
- Workaround: Use
react-native-shadow-2orreact-native-neomorph-shadowswhich provide multi-shadow support. - Alternative: Wrap two nested
Views - the outer one has the dark shadow, the inner one has the light shadow. - Inner shadows for pressed states require SVG-based solutions or pre-rendered images.
Jetpack Compose
@Composable
fun NeuCard() {
val baseColor = Color(0xFFE6E2DD)
Box(
modifier = Modifier
.padding(24.dp)
.shadow(
elevation = 8.dp,
shape = RoundedCornerShape(20.dp),
ambientColor = Color.Black.copy(alpha = 0.15f),
spotColor = Color.Black.copy(alpha = 0.15f),
)
.background(baseColor, RoundedCornerShape(20.dp))
.padding(32.dp)
) {
Column {
Text("Neumorphic Card",
fontSize = 20.sp, fontWeight = FontWeight.SemiBold)
Spacer(Modifier.height(16.dp))
Text("Extruded from the surface itself.",
fontSize = 15.sp, color = Color(0xFF888888))
}
}
}
- Compose limitation: Like React Native, Compose
Modifier.shadow()only supports a single directional shadow. - For true dual-shadow neumorphism, use a custom
Modifier.drawBehind { }withdrawIntoCanvasto paint two separate shadow paths (one light, one dark). - The
neumorphic-composelibrary provides a pre-builtModifier.neumorphic()that handles both shadows. - Set the
Scaffoldbackground to the samebaseColor- this is non-negotiable.
Do's and Don'ts
- DO: Use inset shadows for "active" states (like pressed buttons or filled form fields).
- DON'T: Rely on Neumorphism for critical elements without secondary indicators. Contrast is inherently low, making it an accessibility nightmare if used improperly.
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.
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.