Shape System
Vetra UI's shape system provides a consistent corner radius scale for creating visual hierarchy and comfortable UI elements.
Philosophy
- Comfortable Corners: Soft, approachable rounded corners
- Clear Hierarchy: Different sizes for different component types
- Consistent Rhythm: Predictable, harmonious scale
- Elegant Balance: Not too sharp, not too rounded
Shape Scale
Vetra UI provides 8 predefined corner radius values:
VetraTheme.shapes.none // 0dp - Sharp corners
VetraTheme.shapes.xs // 4dp - Extra small
VetraTheme.shapes.sm // 8dp - Small
VetraTheme.shapes.md // 12dp - Medium
VetraTheme.shapes.lg // 16dp - Large
VetraTheme.shapes.xl // 24dp - Extra large
VetraTheme.shapes.xxl // 32dp - 2X large
VetraTheme.shapes.full // 9999dp - Fully rounded
Shape Definitions
None - 0dp
Sharp corners for specific design needs.
Box(
modifier = Modifier
.size(100.dp)
.background(VetraTheme.colors.brand, shape = VetraTheme.shapes.none)
)
Usage: - Images in grids - Edge-to-edge layouts - When sharp corners are intentional
XS - 4dp
Extra small rounding for tiny elements.
Usage: - Badges - Small chips - Compact UI elements - Status indicators
SM - 8dp
Small rounding for buttons and inputs.
VetraButton(onClick = { }) {
Text("Button") // Uses shapes.sm by default
}
VetraTextField(
value = text,
onValueChange = { text = it },
shape = VetraTheme.shapes.sm
)
Usage: - Buttons (default) - Text fields - Small cards - Chips
MD - 12dp
Medium rounding for cards and containers.
VetraCard(
modifier = Modifier.fillMaxWidth(),
shape = VetraTheme.shapes.md // Default
) {
// Card content
}
Usage: - Cards (default) - Panels - Medium containers - List items
LG - 16dp
Large rounding for prominent cards.
VetraElevatedCard(
modifier = Modifier.size(200.dp),
shape = VetraTheme.shapes.lg
) {
// Featured content
}
Usage: - Large cards - Feature containers - Image containers - Bottom sheets
XL - 24dp
Extra large rounding for dialogs and sheets.
VetraDialog(
onDismissRequest = { },
shape = VetraTheme.shapes.xl // Default
) {
// Dialog content
}
Usage: - Dialogs (default) - Bottom sheets - Modals - Floating panels
XXL - 32dp
Very large rounding for full-screen modals.
Box(
modifier = Modifier
.fillMaxSize()
.background(
VetraTheme.colors.canvasElevated,
shape = VetraTheme.shapes.xxl
)
)
Usage: - Full-screen modals - Large overlays - Splash screens with rounded corners
Full - 9999dp
Fully rounded (circular or pill-shaped).
// Circular
Box(
modifier = Modifier
.size(48.dp)
.background(VetraTheme.colors.brand, shape = VetraTheme.shapes.full)
)
// Pill-shaped
Box(
modifier = Modifier
.height(32.dp)
.width(100.dp)
.background(VetraTheme.colors.accent, shape = VetraTheme.shapes.full)
)
Usage: - Avatar images - Icon buttons (circular) - Pills and tags - Floating action buttons - Progress indicators
Usage Examples
Basic Usage
@Composable
fun ShapeExamples() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Card with medium rounding
VetraCard(shape = VetraTheme.shapes.md) {
Text(
text = "Standard Card",
modifier = Modifier.padding(16.dp)
)
}
// Button with small rounding
VetraButton(onClick = { }) {
Text("Button")
}
// Chip with small rounding
VetraChip(
text = "Chip",
shape = VetraTheme.shapes.sm
)
// Badge with extra small rounding
VetraBadge(
text = "5",
shape = VetraTheme.shapes.xs
)
// Avatar with full rounding
Image(
painter = painterResource("avatar.png"),
contentDescription = "Avatar",
modifier = Modifier
.size(48.dp)
.clip(VetraTheme.shapes.full)
)
}
}
Custom Shapes
// Apply custom shape to any component
Box(
modifier = Modifier
.size(100.dp)
.background(
color = VetraTheme.colors.brandSubtle,
shape = VetraTheme.shapes.lg
)
.vetraShadow(
elevation = VetraTheme.shadows.md,
shape = VetraTheme.shapes.lg
)
)
Clipping Content
// Clip content to shape
Image(
painter = rememberImagePainter("photo.jpg"),
contentDescription = "Photo",
modifier = Modifier
.size(200.dp)
.clip(VetraTheme.shapes.md)
)
// Clip with border
Image(
painter = rememberImagePainter("photo.jpg"),
contentDescription = "Photo",
modifier = Modifier
.size(200.dp)
.border(
width = 2.dp,
color = VetraTheme.colors.border,
shape = VetraTheme.shapes.md
)
.clip(VetraTheme.shapes.md)
)
Animated Shapes
@Composable
fun AnimatedShapeButton() {
var expanded by remember { mutableStateOf(false) }
val cornerRadius by animateDpAsState(
targetValue = if (expanded) 24.dp else 8.dp
)
Box(
modifier = Modifier
.height(48.dp)
.width(if (expanded) 200.dp else 100.dp)
.background(
VetraTheme.colors.brand,
RoundedCornerShape(cornerRadius)
)
.clickable { expanded = !expanded }
)
}
Component Default Shapes
Different components use different default shapes:
| Component | Default Shape | Reasoning |
|---|---|---|
| Button | sm (8dp) |
Comfortable, balanced |
| Card | md (12dp) |
Distinct, not too rounded |
| Dialog | xl (24dp) |
Prominent, friendly |
| TextField | sm (8dp) |
Consistent with buttons |
| Chip | sm (8dp) |
Compact, clear |
| Badge | xs (4dp) |
Small, subtle |
| Icon Button | full |
Circular, traditional |
| Avatar | full |
Circular standard |
| Menu | md (12dp) |
Comfortable dropdown |
| Bottom Sheet | xl (24dp) |
Friendly, accessible |
Customization
Custom Shape Scale
val CustomShapes = DefaultVetraShapes.copy(
sm = RoundedCornerShape(6.dp), // Less rounded buttons
md = RoundedCornerShape(16.dp), // More rounded cards
full = RoundedCornerShape(50) // Perfect circles
)
VetraTheme(shapes = CustomShapes) {
// Your app with custom shapes
}
Per-Component Shapes
// Override shape for specific component
VetraButton(
onClick = { },
modifier = Modifier
.clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp))
) {
Text("Custom Shape")
}
Complex Shapes
// Different corners
val customShape = RoundedCornerShape(
topStart = 16.dp,
topEnd = 16.dp,
bottomStart = 0.dp,
bottomEnd = 0.dp
)
Box(
modifier = Modifier
.size(100.dp)
.background(VetraTheme.colors.brand, customShape)
)
// Cutout shape
val cutoutShape = RoundedCornerShape(
topStart = 16.dp,
topEnd = 0.dp,
bottomEnd = 16.dp,
bottomStart = 0.dp
)
Guidelines
Size Selection
Choose shapes based on component size:
// Small components (< 40dp)
VetraBadge(shape = VetraTheme.shapes.xs)
// Medium components (40-100dp)
VetraButton(shape = VetraTheme.shapes.sm)
// Large components (100-300dp)
VetraCard(shape = VetraTheme.shapes.md)
// Very large components (> 300dp)
VetraDialog(shape = VetraTheme.shapes.xl)
Consistency
Maintain consistent shapes for similar components:
// ✅ Consistent
VetraButton(onClick = { }) { Text("Save") }
VetraSecondaryButton(onClick = { }) { Text("Cancel") }
// Both use shapes.sm
// ❌ Inconsistent
VetraButton(onClick = { }) { Text("Save") } // shapes.sm
Box(
modifier = Modifier
.background(color, RoundedCornerShape(20.dp)) // Random size
) { Text("Cancel") }
Visual Hierarchy
Use shapes to reinforce hierarchy:
Column {
// Primary action - standard rounding
VetraButton(onClick = { }) {
Text("Continue")
}
// Secondary action - same rounding for consistency
VetraOutlinedButton(onClick = { }) {
Text("Go Back")
}
// Info card - slightly more rounded
VetraCard(shape = VetraTheme.shapes.lg) {
Text("Additional information")
}
}
Best Practices
✅ Do
- Use predefined shape values
- Match shape to component size
- Maintain consistency across similar elements
- Consider the overall visual rhythm
- Use
fullfor avatars and circular buttons - Clip images to shapes for polish
❌ Don't
- Create random corner radius values
- Mix too many different shapes
- Use excessive rounding (except for
full) - Apply sharp corners without reason
- Ignore the scale for custom components
- Over-round small components
Accessibility
Shapes don't directly impact accessibility, but consider:
- Touch Targets: Ensure touch areas are at least 44×44dp regardless of visual shape
- Focus Indicators: Shape should accommodate focus rings
- Consistency: Predictable shapes help users understand interactivity
// Touch target is larger than visual shape
VetraButton(
onClick = { },
modifier = Modifier
.defaultMinSize(minWidth = 88.dp, minHeight = 44.dp) // Minimum touch target
) {
Text("Button")
}
Migration from Material
// Material
Button(
onClick = { },
shape = MaterialTheme.shapes.medium
)
// Vetra
VetraButton(
onClick = { }
) // Uses VetraTheme.shapes.sm automatically
Next: Learn about Elevation →