| name | tile-design |
| description | Web and App implementation guide for Tile Design. Trigger when user wants Microsoft Metro style, sharp square information units, and horizontal scrolling grids. |
| date_added | 2026-06-17 |
| risk | safe |
| source | self |
| source_type | self |
Tile Design (Metro UI)
"Authentically digital. Clean, sharp squares relying purely on typography and flat color."
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
- Sharp Corners: Absolutely no border-radius. Everything is a perfect square or sharp rectangle.
- Live Data: Tiles flip, scroll, or fade internally to show live updates without the user interacting.
- Horizontal Panning: The grid often expands infinitely to the right, encouraging horizontal scrolling.
Visual DNA
- Colors: High saturation, flat colors. A dark background (pure black) with bright cyan, magenta, orange, and green tiles.
- Typography: Extremely clean, light sans-serifs (like
Segoe UI Light). Text is almost always pure white.
- Icons: Simple, wireframe, monochromatic glyphs placed centrally or in the corner.
Web Implementation
body {
background-color: #111;
color: #fff;
font-family: 'Segoe UI', sans-serif;
overflow-x: auto;
}
.tile-group {
display: grid;
grid-template-columns: repeat(4, 150px);
grid-auto-rows: 150px;
gap: 8px;
padding: 40px;
}
.tile {
background-color: #0078D7;
padding: 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
cursor: pointer;
transition: transform 0.1s;
transform-origin: center;
}
.tile:active {
transform: scale(0.95);
}
.tile-wide { grid-column: span 2; }
.tile-large { grid-column: span 2; grid-row: span 2; }
.tile-live-content {
: slideUp infinite;
}
slideUp {
, { : (); }
, { : (-); }
{ : (); }
}
App Implementation
SwiftUI
struct TileDesignView: View {
let rows = [GridItem(.fixed(150), spacing: 8), GridItem(.fixed(150), spacing: 8)]
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
LazyHGrid(rows: rows, spacing: 8) {
TileView(title: "Mail", color: Color(hex: "0078D7"), icon: "envelope")
TileView(title: "Photos", color: Color(hex: "00CC6A"), icon: "photo", isLarge: true)
TileView(title: "Weather", color: Color(hex: "2D7D9A"), icon: "cloud.sun")
TileView(title: "Calendar", color: Color(hex: "D13438"), icon: "calendar")
}
.padding(40)
}
.background(Color(hex: "111111").ignoresSafeArea())
}
}
struct TileView: View {
let title: String
let color: Color
let icon:
isLarge:
isPressed
body: {
(alignment: .leading) {
(systemName: icon)
.font(.system(size: , weight: .light))
.foregroundColor(.white)
()
(title)
.font(.custom(, size: ))
.foregroundColor(.white)
}
.padding()
.frame(width: isLarge : , height: isLarge : , alignment: .leading)
.background(color)
.scaleEffect(isPressed : )
.animation(.spring(response: , dampingFraction: ), value: isPressed)
.onLongPressGesture(minimumDuration: .infinity, maximumDistance: .infinity, pressing: { pressing
isPressed pressing
}, perform: {})
}
}
- A
LazyHGrid inside a horizontal ScrollView perfectly replicates the Windows Phone / Windows 8 start screen.
- Absolutely NO corner radius.
- The
isPressed state triggering a .scaleEffect(0.95) replicates the physical "tilt" interaction of Metro tiles.
Flutter
class TileDesignScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF111111),
body: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.all(40),
child: SizedBox(
height: 308, // Two rows of 150px + 8px spacing
child: Wrap(
direction: Axis.vertical,
spacing: 8,
runSpacing: 8,
children: [
_buildTile('Mail', const Color(0xFF0078D7), Icons.mail_outline),
_buildTile('Weather', const Color(0xFF2D7D9A), Icons.cloud_outlined),
_buildTile('Photos', const Color(0xFF00CC6A), Icons.photo_outlined, isLarge: true),
_buildTile('Calendar', const Color(0xFFD13438), Icons.calendar_today),
],
),
),
),
);
}
Widget _buildTile(String title, Color color, IconData icon, {bool isLarge = false}) {
return StatefulBuilder(
builder: (context, setState) {
bool isPressed = false;
return GestureDetector(
onTapDown: (_) => setState(() => isPressed = true),
onTapUp: (_) => setState(() => isPressed = false),
onTapCancel: () => setState(() => isPressed = false),
child: AnimatedScale(
scale: isPressed ? 0.95 : 1.0,
duration: const Duration(milliseconds: 100),
child: Container(
width: isLarge ? 308 : 150,
height: isLarge ? 308 : 150,
color: color, // Sharp corners
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(icon, color: Colors.white, size: 32),
Text(title, style: const TextStyle(color: Colors.white, fontFamily: 'Segoe UI', fontSize: 16)),
],
),
),
),
);
}
);
}
}
Wrap with direction: Axis.vertical inside a horizontally scrolling SizedBox is the easiest way to build a Metro grid that flows left-to-right.
- Wrap tiles in
GestureDetector and AnimatedScale to handle the press animation.
React Native
const TileDesignScreen = () => {
return (
<ScrollView horizontal style={{ flex: 1, backgroundColor: '#111' }} contentContainerStyle={{ padding: 40 }}>
<View style={{ flexDirection: 'column', flexWrap: 'wrap', height: 308, gap: 8 }}>
<Tile title="Mail" color="#0078D7" />
<Tile title="Weather" color="#2D7D9A" />
<Tile title="Photos" color="#00CC6A" isLarge />
<Tile title="Calendar" color="#D13438" />
</View>
</ScrollView>
);
};
const Tile = () => {
scale = ( .()).;
= () => .(scale, { : , : }).();
= () => .(scale, { : , : }).();
(
);
};
- Use a
<ScrollView horizontal> combined with a child <View> that has a fixed height and flexWrap: 'wrap', flexDirection: 'column'. This forces children to form columns and flow horizontally.
- Use
Animated.View and TouchableWithoutFeedback to create the scale animation.
Jetpack Compose
@Composable
fun TileDesignScreen() {
LazyHorizontalGrid(
rows = GridCells.Fixed(2),
modifier = Modifier.fillMaxSize().background(Color(0xFF111111)),
contentPadding = PaddingValues(40.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
item(span = { GridItemSpan(1) }) { Tile("Mail", Color(0xFF0078D7)) }
item(span = { GridItemSpan(2) }) { Tile("Photos Wide", Color(0xFF00CC6A)) }
item(span = { GridItemSpan(1) }) { Tile("Weather", Color(0xFF2D7D9A)) }
}
}
@Composable
fun Tile(title: String, color: Color) {
var isPressed by remember { mutableStateOf(false) }
val scale by animateFloatAsState(if (isPressed) 0.95f else 1.0f)
Box(
modifier = Modifier
.size(150.dp)
.scale(scale)
.background(color)
.pointerInput(Unit) {
detectTapGestures(
onPress = {
isPressed =
tryAwaitRelease()
isPressed =
}
)
}
.padding(dp)
) {
Box(modifier = Modifier.size(dp).background(Color.White.copy(alpha = )).align(Alignment.TopStart))
Text(
text = title,
color = Color.White,
fontFamily = FontFamily.SansSerif,
modifier = Modifier.align(Alignment.BottomStart)
)
}
}
LazyHorizontalGrid is the right tool, though building true 2x2 "Large" tiles requires custom layout math in Compose if mixing with 1x1 tiles.
Modifier.scale() paired with pointerInput detectTapGestures handles the Metro interaction.
Do's and Don'ts
- DO: Place the tile label text strictly in the bottom-left corner of the tile.
- DON'T: Add drop shadows or gradients to the tiles.
Limitations
- This is a styling reference and does not replace enprojectnment-specific validation, accessibility testing, or expert review.
- Ensure appropriate contrast ratios and responsive behaviors are verified separately.