281 lines
9.9 KiB
Markdown
281 lines
9.9 KiB
Markdown
---
|
|
name: skeuomorphism
|
|
description: Web and App implementation guide for Skeuomorphism. Trigger when user wants UI to mimic real-world objects, realistic textures, or physical metaphors.
|
|
date_added: "2026-06-17"
|
|
risk: safe
|
|
source: self
|
|
source_type: self
|
|
---
|
|
|
|
# Skeuomorphism
|
|
|
|
> "Digital interfaces that look and behave like their physical counterparts."
|
|
|
|
|
|
## 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. **Realistic Textures**: Leather, brushed metal, wood grain, paper. The UI should feel like a physical object you can touch.
|
|
2. **Physical Lighting & Depth**: Intense attention to specular highlights, drop shadows, inner shadows, bevels, and ambient occlusion.
|
|
3. **Real-world Metaphors**: Switches that look like hardware toggles, dials with physical notches, notepads with binding rings.
|
|
|
|
## Visual DNA
|
|
- **Colors**: Highly dependent on the material being simulated. For rich, classic skeuomorphism, use the **Industrial Chic** (for metal/hardware) or **Monochromatic Brown** (for wood/leather) palettes.
|
|
- **Typography**: Fonts that match the physical object (e.g., typewriter fonts for paper, LCD fonts for digital screens, embossed sans-serifs for hardware buttons).
|
|
- **Details**: Screws, stitching, glare, and gradients are your primary tools.
|
|
|
|
## Web Implementation
|
|
- Heavy use of layered background images (textures), complex gradients, and multiple box-shadows.
|
|
- **CSS Example**:
|
|
```css
|
|
.skeuo-button {
|
|
/* Brushed metal effect */
|
|
background: linear-gradient(180deg, #e0e0e0 0%, #a0a0a0 100%),
|
|
url('brushed-metal-texture.png');
|
|
background-blend-mode: overlay;
|
|
|
|
border: 1px solid #7a7a7a;
|
|
border-radius: 50%;
|
|
width: 80px;
|
|
height: 80px;
|
|
|
|
/* Bevel, inner highlight, and drop shadow */
|
|
box-shadow:
|
|
inset 0 2px 4px rgba(255,255,255,0.8), /* Top highlight */
|
|
inset 0 -2px 4px rgba(0,0,0,0.4), /* Bottom shading */
|
|
0 4px 6px rgba(0,0,0,0.5), /* Drop shadow */
|
|
0 1px 1px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.skeuo-button:active {
|
|
/* Pressing the physical button */
|
|
box-shadow:
|
|
inset 0 4px 8px rgba(0,0,0,0.6),
|
|
inset 0 -1px 2px rgba(255,255,255,0.4),
|
|
0 1px 1px rgba(0,0,0,0.2);
|
|
transform: translateY(2px);
|
|
}
|
|
```
|
|
|
|
## App Implementation
|
|
|
|
### SwiftUI
|
|
```swift
|
|
struct SkeuoButton: View {
|
|
@State private var isPressed = false
|
|
|
|
var body: some View {
|
|
Button(action: {}) {
|
|
Text("POWER")
|
|
.font(.system(size: 14, weight: .bold))
|
|
.foregroundColor(.white)
|
|
.textCase(.uppercase)
|
|
}
|
|
.frame(width: 80, height: 80)
|
|
.background(
|
|
ZStack {
|
|
// Brushed metal base
|
|
Circle()
|
|
.fill(
|
|
LinearGradient(
|
|
colors: [Color(white: 0.88), Color(white: 0.63)],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
)
|
|
// Inner highlight (top bevel)
|
|
Circle()
|
|
.stroke(
|
|
LinearGradient(
|
|
colors: [.white.opacity(0.8), .clear],
|
|
startPoint: .top,
|
|
endPoint: .center
|
|
),
|
|
lineWidth: 2
|
|
)
|
|
.padding(1)
|
|
}
|
|
)
|
|
.clipShape(Circle())
|
|
// Outer bezel ring
|
|
.overlay(Circle().stroke(Color(white: 0.5), lineWidth: 1))
|
|
// Physical drop shadow
|
|
.shadow(color: .black.opacity(isPressed ? 0.2 : 0.5), radius: isPressed ? 2 : 6,
|
|
x: 0, y: isPressed ? 1 : 4)
|
|
.scaleEffect(isPressed ? 0.96 : 1.0)
|
|
.animation(.easeOut(duration: 0.1), value: isPressed)
|
|
.simultaneousGesture(
|
|
DragGesture(minimumDistance: 0)
|
|
.onChanged { _ in isPressed = true }
|
|
.onEnded { _ in isPressed = false }
|
|
)
|
|
}
|
|
}
|
|
```
|
|
- Stack multiple shapes (`Circle`, `RoundedRectangle`) with different gradients to build up realistic depth.
|
|
- Use `.overlay()` with stroked shapes for highlight bezels along the edges.
|
|
- The pressed state should reduce shadow AND scale — simulating a physical push.
|
|
|
|
### Flutter
|
|
```dart
|
|
class SkeuoButton extends StatefulWidget {
|
|
@override
|
|
State<SkeuoButton> createState() => _SkeuoButtonState();
|
|
}
|
|
|
|
class _SkeuoButtonState extends State<SkeuoButton> {
|
|
bool _isPressed = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTapDown: (_) => setState(() => _isPressed = true),
|
|
onTapUp: (_) => setState(() => _isPressed = false),
|
|
onTapCancel: () => setState(() => _isPressed = false),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 100),
|
|
width: 80,
|
|
height: 80,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
// Brushed metal gradient
|
|
gradient: LinearGradient(
|
|
colors: [Colors.grey[300]!, Colors.grey[600]!],
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
),
|
|
border: Border.all(color: Colors.grey[500]!, width: 1),
|
|
boxShadow: [
|
|
// Outer drop shadow
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(_isPressed ? 0.2 : 0.5),
|
|
blurRadius: _isPressed ? 4 : 12,
|
|
offset: Offset(0, _isPressed ? 2 : 6),
|
|
),
|
|
// Inner top highlight (faked with a light inset)
|
|
BoxShadow(
|
|
color: Colors.white.withOpacity(0.6),
|
|
blurRadius: 1,
|
|
offset: const Offset(0, -1),
|
|
spreadRadius: -1,
|
|
),
|
|
],
|
|
),
|
|
transform: Matrix4.identity()..scale(_isPressed ? 0.96 : 1.0),
|
|
alignment: Alignment.center,
|
|
child: const Text('POWER',
|
|
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold,
|
|
fontSize: 14, shadows: [
|
|
Shadow(color: Colors.black54, offset: Offset(0, 1), blurRadius: 2),
|
|
])),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
```
|
|
- Use `AnimatedContainer` for smooth press transitions. Adjust `boxShadow`, `transform`, and gradient on tap.
|
|
- Layer `BoxShadow` entries: one for the outer drop shadow, one for the inner top-edge highlight.
|
|
- For complex textures (leather, wood), use `DecorationImage` with an asset file inside `BoxDecoration`.
|
|
|
|
### React Native
|
|
```jsx
|
|
const SkeuoButton = () => {
|
|
const [pressed, setPressed] = useState(false);
|
|
|
|
return (
|
|
<Pressable
|
|
onPressIn={() => setPressed(true)}
|
|
onPressOut={() => setPressed(false)}
|
|
style={{
|
|
width: 80,
|
|
height: 80,
|
|
borderRadius: 40,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
// Metal gradient must be done via an image or LinearGradient component
|
|
backgroundColor: '#A0A0A0',
|
|
borderWidth: 1,
|
|
borderColor: '#7A7A7A',
|
|
// Shadow changes on press
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: pressed ? 1 : 4 },
|
|
shadowOpacity: pressed ? 0.2 : 0.5,
|
|
shadowRadius: pressed ? 2 : 6,
|
|
elevation: pressed ? 2 : 8,
|
|
transform: [{ scale: pressed ? 0.96 : 1 }],
|
|
}}
|
|
>
|
|
<Text style={{
|
|
color: '#FFF',
|
|
fontWeight: '700',
|
|
fontSize: 14,
|
|
textShadowColor: 'rgba(0,0,0,0.5)',
|
|
textShadowOffset: { width: 0, height: 1 },
|
|
textShadowRadius: 2,
|
|
}}>
|
|
POWER
|
|
</Text>
|
|
</Pressable>
|
|
);
|
|
};
|
|
```
|
|
- For realistic textures, use `ImageBackground` with exported texture assets (leather.png, brushed-metal.png).
|
|
- Use `Pressable` with `onPressIn`/`onPressOut` to animate shadow depth, scale, and opacity changes.
|
|
- Complex gradient bevels require `react-native-linear-gradient` or `expo-linear-gradient`.
|
|
|
|
### Jetpack Compose
|
|
```kotlin
|
|
@Composable
|
|
fun SkeuoButton() {
|
|
var isPressed by remember { mutableStateOf(false) }
|
|
val scale by animateFloatAsState(if (isPressed) 0.96f else 1f)
|
|
val elevation by animateDpAsState(if (isPressed) 2.dp else 8.dp)
|
|
|
|
Box(
|
|
modifier = Modifier
|
|
.size(80.dp)
|
|
.graphicsLayer { scaleX = scale; scaleY = scale }
|
|
.shadow(elevation, CircleShape)
|
|
.clip(CircleShape)
|
|
.background(
|
|
Brush.verticalGradient(
|
|
colors = listOf(Color(0xFFE0E0E0), Color(0xFFA0A0A0))
|
|
)
|
|
)
|
|
.border(1.dp, Color(0xFF7A7A7A), CircleShape)
|
|
.pointerInput(Unit) {
|
|
detectTapGestures(
|
|
onPress = {
|
|
isPressed = true
|
|
tryAwaitRelease()
|
|
isPressed = false
|
|
}
|
|
)
|
|
},
|
|
contentAlignment = Alignment.Center,
|
|
) {
|
|
Text("POWER",
|
|
color = Color.White,
|
|
fontWeight = FontWeight.Bold,
|
|
fontSize = 14.sp,
|
|
style = TextStyle(shadow = Shadow(
|
|
color = Color.Black.copy(alpha = 0.5f),
|
|
offset = Offset(0f, 2f),
|
|
blurRadius = 4f,
|
|
)))
|
|
}
|
|
}
|
|
```
|
|
- Use `Brush.verticalGradient()` for metallic surfaces and `Modifier.border()` with `CircleShape` for bezels.
|
|
- Animate `shadow` elevation and `graphicsLayer { scaleX/scaleY }` on press for realistic physical push.
|
|
- For textures, use `Modifier.paint(painterResource(R.drawable.brushed_metal))` as a background.
|
|
|
|
## Do's and Don'ts
|
|
- **DO**: Ensure the metaphor makes sense for the user's task.
|
|
- **DON'T**: Overuse it to the point of clutter. Keep the interactive elements highly realistic, but let the structural layout remain clean.
|
|
|
|
## 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.
|