Skill

Build UI components from widget-based design patterns

Widget-Based Design sub-style for design-it: glanceable app-let widgets in strict aspect ratios, with a Flutter grid-layout gotcha.

Maintainer of this project? Claim this page to edit the listing.


81
Spark score
out of 100
Updated 26 days ago
Version 13.6.1

Add to Favorites

Why it matters

Help developers translate widget-based design systems into production-ready UI components by providing structured guidance on component architecture, state management, and reusable pattern implementation.

Outcomes

What it gets done

01

Convert widget specifications into component code with proper props and state

02

Structure reusable UI patterns following design system conventions

03

Implement component composition and layout hierarchies from widget trees

04

Generate accessible, maintainable code that matches design intent

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/ag-widget-based-design | bash

Overview

Widget-Based Design

A design-it sub-style for glanceable, iOS-style widget grids with strict aspect ratios, including a Flutter-specific grid-layout package recommendation. Use for a widget-style dashboard of small, glanceable, functionally-scoped tiles; keep complex forms and lists out of 1x1 widgets.

What it does

Widget-Based Design is a design-it sub-style modeled on iOS/home-screen widgets: strict aspect ratios (1x1 square, 2x1 rectangle, 2x2 large square), glanceability (each widget surfaces its single most important data point instantly, with deeper interaction deferred to opening the full app), and corner-radius matching so inner content nests cleanly within the widget's outer rounded corner. It is a child reference of design-it, applied only when a request matches this functional, app-let-focused aesthetic - similar to Bento UI but specifically about miniature functional apps rather than general content layout.

When to use - and when NOT to

Use this sub-style for dashboards or home screens made of small, glanceable, rearrangeable functional blocks - a weather tile showing a large temperature number, a fitness tile, a music-player tile - each using bright solid colors or full-bleed photography to visually separate one widget from another. The style's own rule draws a hard line on scope: never put complex forms or scrollable lists inside a 1x1 widget - that data belongs in the full app, not the glanceable summary.

Inputs and outputs

On web, a CSS grid with grid-auto-rows: 160px and grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)) forces square base units, with widget-small/widget-medium/widget-large classes spanning 1, 2, or 2x2 cells, all using the "classic iOS widget radius" of 24px. SwiftUI's LazyVGrid and Jetpack Compose's LazyVerticalGrid (with GridItemSpan) both natively support the mixed-size widget grid and are described as matching Apple's default widget styling closely. Flutter is the outlier: its standard GridView.count does not easily support widgets spanning multiple rows or columns, so the skill recommends the flutter_staggered_grid_view package for any real production widget layout rather than fighting the built-in grid. React Native builds the same layout with flexWrap: 'wrap' and percentage widths (47% for two columns with a gap), and requires StyleSheet.absoluteFillObject on any LinearGradient background so it sits correctly behind the widget's text content.

.widget-large { grid-column: span 2; grid-row: span 2; }
.widget { border-radius: 24px; }

Integrations

As a design-it sub-style, it's applied through the parent skill's aesthetic matching, with the mixed-size widget grid expected across web CSS and the four listed native frameworks - noting that Flutter alone needs a third-party package where the others rely on native grid span support.

Who it's for

Designers and developers building a widget-style dashboard or home-screen surface - glanceable, rearrangeable, functionally-scoped tiles rather than a general content grid - who need the mixed-size grid layout reproduced across web and native platforms, including Flutter's specific need for flutter_staggered_grid_view to handle spanning widgets that its other frameworks support out of the box.

Source README

Widget-Based Design

"Miniature applications. Small, highly functional blocks of UI designed to be rearranged."

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. Strict Aspect Ratios: Widgets usually follow strict sizes (1x1 square, 2x1 rectangle, 2x2 large square).
  2. Glanceability: Widgets show the most important piece of data instantly. Deep interaction usually requires opening the full app.
  3. Corner Radius Match: The inner content's border radius should perfectly nest within the widget's outer border radius.

Visual DNA

  • Colors: Widgets often use bright, solid color backgrounds or full-bleed photos to differentiate themselves.
  • Typography: Large, bold numbers (like a clock or weather temp) paired with tiny sub-labels.
  • Layout: Similar to Bento UI, but specifically focused on functional app-lets rather than just content layout.

Web Implementation

  • CSS Example:
.widget-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  grid-auto-rows: 160px; /* Force squares */
  gap: 16px;
  padding: 32px;
}

.widget {
  background-color: #ffffff;
  border-radius: 24px; /* Classic iOS widget radius */
  box-shadow: 0 8px 24px rgba(0,0,0,0.08);
  padding: 16px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  overflow: hidden;
  position: relative;
}

/* Specific Sizes */
.widget-small { grid-column: span 1; grid-row: span 1; }
.widget-medium { grid-column: span 2; grid-row: span 1; }
.widget-large { grid-column: span 2; grid-row: span 2; }

/* Weather Widget Example */
.widget.weather {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  color: white;
}
.weather-temp { font-size: 3rem; font-weight: 300; }
.weather-icon { position: absolute; top: 16px; right: 16px; font-size: 2rem; }

App Implementation

SwiftUI

struct WidgetDesignView: View {
    let columns = [GridItem(.flexible(), spacing: 16), GridItem(.flexible(), spacing: 16)]
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 16) {
                // 2x1 Widget
                WeatherWidget()
                    .frame(height: 160) // Base unit height
                
                // 1x1 Widget
                FitnessWidget()
                    .frame(height: 160)
                
                // 1x1 Widget
                MusicWidget()
                    .frame(height: 160)
            }
            .padding(24)
        }
        .background(Color(white: 0.95))
    }
}

struct WeatherWidget: View {
    var body: some View {
        ZStack(alignment: .topTrailing) {
            LinearGradient(colors: [Color(hex: "4facfe"), Color(hex: "00f2fe")], startPoint: .topLeading, endPoint: .bottomTrailing)
            
            Image(systemName: "cloud.sun.fill")
                .foregroundColor(.white)
                .font(.system(size: 40))
                .padding()
            
            VStack(alignment: .leading) {
                Spacer()
                Text("72°")
                    .font(.system(size: 48, weight: .thin))
                    .foregroundColor(.white)
                Text("San Francisco")
                    .font(.subheadline)
                    .foregroundColor(.white.opacity(0.8))
            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding()
        }
        .cornerRadius(24) // Classic iOS widget radius
        .shadow(color: .black.opacity(0.1), radius: 10, y: 5)
    }
}
  • SwiftUI is perfect for this. The LazyVGrid handles the layout, and cornerRadius(24) matches Apple's default widget styling perfectly.
  • Use ZStack as the root of the widget to easily overlay content on top of complex gradients or images.

Flutter

class WidgetDesignScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFFF2F2F2),
      body: GridView.count(
        crossAxisCount: 2, // 2 columns for a typical phone
        padding: const EdgeInsets.all(24),
        mainAxisSpacing: 16,
        crossAxisSpacing: 16,
        childAspectRatio: 1.0, // 1x1 squares
        children: [
          // Note: Flutter GridView doesn't easily span rows/cols out of the box.
          // flutter_staggered_grid_view is highly recommended for real widget layouts.
          _buildWeatherWidget(),
          _buildFitnessWidget(),
          _buildMusicWidget(),
        ],
      ),
    );
  }

  Widget _buildWeatherWidget() {
    return Container(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(24),
        gradient: const LinearGradient(colors: [Color(0xFF4FACFE), Color(0xFF00F2FE)]),
        boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 5))],
      ),
      padding: const EdgeInsets.all(16),
      child: Stack(
        children: [
          const Align(
            alignment: Alignment.topRight,
            child: Icon(Icons.cloud, color: Colors.white, size: 40),
          ),
          Align(
            alignment: Alignment.bottomLeft,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text('72°', style: TextStyle(color: Colors.white, fontSize: 48, fontWeight: FontWeight.w300)),
                Text('San Francisco', style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 14)),
              ],
            ),
          )
        ],
      ),
    );
  }
}
  • A Stack inside a Container with BorderRadius.circular(24) is the blueprint for every widget.
  • Standard GridView is too rigid for mixed 2x1 and 1x1 widgets. Use the flutter_staggered_grid_view package for production apps.

React Native

const WidgetDesignScreen = () => {
  return (
    <ScrollView style={{ flex: 1, backgroundColor: '#F2F2F2' }} contentContainerStyle={{ padding: 24 }}>
      <View style={{ flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', gap: 16 }}>
        
        {/* 2x1 Widget (Full width minus padding) */}
        <View style={[styles.widget, { width: '100%' }]}>
          <LinearGradient colors={['#4facfe', '#00f2fe']} style={styles.widgetBg} />
          <Text style={styles.temp}>72°</Text>
          <Text style={styles.sub}>San Francisco</Text>
        </View>

        {/* 1x1 Widgets (Half width minus half gap) */}
        <View style={[styles.widget, { width: '47%' }]}><Text>Fitness</Text></View>
        <View style={[styles.widget, { width: '47%' }]}><Text>Music</Text></View>

      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  widget: {
    height: 160, // Fixed height forces square/rectangular aspect ratios
    borderRadius: 24,
    backgroundColor: '#FFF',
    padding: 16,
    justifyContent: 'flex-end',
    shadowColor: '#000', shadowOffset: { width: 0, height: 5 }, shadowOpacity: 0.1, shadowRadius: 10, elevation: 5,
    overflow: 'hidden'
  },
  widgetBg: {
    ...StyleSheet.absoluteFillObject,
    borderRadius: 24,
  },
  temp: { fontSize: 48, fontWeight: '300', color: '#FFF' },
  sub: { fontSize: 14, color: 'rgba(255,255,255,0.8)' }
});
  • In React Native, flexWrap: 'wrap' combined with percentage widths (e.g., 47% for 2 columns with a gap) is the cleanest way to build a responsive widget layout.
  • If using LinearGradient from expo-linear-gradient or react-native-linear-gradient, apply StyleSheet.absoluteFillObject so it sits behind the text.

Jetpack Compose

@Composable
fun WidgetDesignScreen() {
    LazyVerticalGrid(
        columns = GridCells.Fixed(2),
        modifier = Modifier.fillMaxSize().background(Color(0xFFF2F2F2)).padding(24.dp),
        horizontalArrangement = Arrangement.spacedBy(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        // 2x1 Widget (Spans both columns)
        item(span = { GridItemSpan(2) }) {
            WeatherWidget(Modifier.height(160.dp))
        }
        // 1x1 Widgets
        item { Box(Modifier.height(160.dp).background(Color.White, RoundedCornerShape(24.dp))) }
        item { Box(Modifier.height(160.dp).background(Color.White, RoundedCornerShape(24.dp))) }
    }
}

@Composable
fun WeatherWidget(modifier: Modifier = Modifier) {
    Box(
        modifier = modifier
            .fillMaxWidth()
            .shadow(10.dp, RoundedCornerShape(24.dp))
            .background(Brush.linearGradient(listOf(Color(0xFF4FACFE), Color(0xFF00F2FE))), RoundedCornerShape(24.dp))
            .padding(16.dp)
    ) {
        // Icon
        Icon(Icons.Filled.Cloud, contentDescription = null, tint = Color.White, modifier = Modifier.align(Alignment.TopEnd).size(40.dp))
        
        // Data
        Column(modifier = Modifier.align(Alignment.BottomStart)) {
            Text("72°", fontSize = 48.sp, fontWeight = FontWeight.Light, color = Color.White)
            Text("San Francisco", fontSize = 14.sp, color = Color.White.copy(alpha = 0.8f))
        }
    }
}
  • LazyVerticalGrid with GridCells.Fixed(2) and GridItemSpan perfectly recreate iOS widget grids.
  • RoundedCornerShape(24.dp) is essential to sell the look.

Do's and Don'ts

  • DO: Use full-bleed background colors to make different widgets stand out.
  • DON'T: Put complex forms or scrollable lists inside a 1x1 widget.

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.