Vize

Vize is a modern, developer-friendly Flutter package for effortless responsive UIs that match your Figma designs perfectly.

It uses percentage-based sizing, smart scaling for padding and typography, adaptive grids, breakpoint overrides, handy extensions, and responsive builders, making it smooth across mobile, tablet and desktop views.

Perfect for clean, production-ready apps and scalable design systems. No more responsiveness headaches!

Pub Points Build Status pub package License: MIT

Features

  • Percentage-Based Layouts: Intuitive width/height sizing
  • Figma Scaling: Direct scaling from your designs
  • Device Detection: Automatic mobile, tablet, desktop detection
  • Orientation Support: Portrait and landscape handling
  • Elegant Source: Clean, concise syntax with extensions
  • Lightweight: Minimal overhead, maximum performance
  • Flexible: Customizable breakpoints and scaling
  • User Font Scaling: App-wide text size preference via textScalar

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  vize: ^1.0.3

Then run:

flutter pub get

Quick Start

1. Initialize Vize

Initialize Vize once in your app, typically in your root widget:

import 'package:flutter/material.dart';
import 'package:vize/vize.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // Initialize Vize
    Vize.init(context);

    return const MaterialApp(
      home: HomePage(),
    );
  }
}

2. Use Vize Helpers

Now you can use Vize's responsive helpers throughout your app:

Container(
  width: w(50),      // 50% of screen width
  height: h(30),     // 30% of screen height
  padding: pa(16),   // Scaled 16px padding
  child: Text(
    'Hello Vize!',
    style: const TextStyle(fontSize: ts(18)), // Scaled 18px text
  ),
)

3. Use Extensions (Optional)

For even cleaner code, use Vize's number extensions:

Container(
  width: 50.w,       // 50% of screen width
  height: 30.h,      // 30% of screen height
  padding: 16.pa,    // Scaled 16px padding
  child: Text(
    'Hello Vize!',
    style: const TextStyle(fontSize: 18.ts), // Scaled 18px text
  ),
)

Usage Examples

Percentage-Based Layouts

Perfect for flexible, responsive designs:

Column(
  children: [
    Container(
      width: w(100),  // Full width
      height: h(25),  // 25% of screen height
      color: Colors.blue,
    ),
    hs(2),  // 2% height spacing
    Container(
      width: w(80),   // 80% width
      height: h(50),  // 50% height
      color: Colors.green,
    ),
  ],
)

Figma Scaling

Scale your designs directly from Figma:

// Using Vize.I
Container(
  width: Vize.I.sw(200),   // Scale 200px from Figma
  height: Vize.I.sh(100),  // Scale 100px from Figma
  padding: Vize.I.pa(16),  // Scale 16px padding
  decoration: BoxDecoration(
    borderRadius: BorderRadius.circular(Vize.I.r(12)), // Scale 12px radius
  ),
)

// Or with extensions
Container(
  width: 200.fws,
  height: 100.fhs,
  padding: 16.pa,
  decoration: BoxDecoration(
    borderRadius: BorderRadius.circular(12.r),
  ),
)

Device-Specific Layouts

Build different layouts for different devices:

VizeBuilder(
  mobile: (context) => const MobileLayout(),
  tablet: (context) => const TabletLayout(),
  desktop: (context) => const DesktopLayout(),
)

Or use conditional logic:

@override
Widget build(BuildContext context) {
  if (isMobile) {
    return const MobileLayout();
  } else if (isTablet) {
    return const TabletLayout();
  } else {
    return const DesktopLayout();
  }
}

Responsive Padding

Multiple ways to add responsive padding:

// All sides
Container(padding: pa(16))

// Symmetric
Container(padding: ps(h: 20, v: 10))

// Individual sides
Container(padding: po(l: 10, t: 20, r: 10, b: 20))

// With extensions
Container(padding: 16.pa)

Spacing

Easy spacing between widgets:

Column(
  children: [
    const Text('Item 1'),
    hs(2),  // 2% height spacing
    const Text('Item 2'),
    hs(3),  // 3% height spacing
    const Text('Item 3'),
  ],
)

Row(
  children: [
    const Text('A'),
    ws(5),  // 5% width spacing
    const Text('B'),
  ],
)

Standard Spacing (8px Grid)

Use the sp() helper for standard spacing:

Column(
  children: [
    const Text('Item 1'),
    SizedBox(height: sp()),    // 8px scaled
    const Text('Item 2'),
    SizedBox(height: sp(2)),   // 16px scaled
    const Text('Item 3'),
    SizedBox(height: sp(3)),   // 24px scaled
  ],
)

Adaptive Grid

Create responsive grid layouts:

GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: adaptiveColumns(
      mobile: 2,
      tablet: 4,
      desktop: 6,
    ),
    crossAxisSpacing: sp(2),
    mainAxisSpacing: sp(2),
  ),
  itemBuilder: (context, index) => const Card(child: Text('Item')),
)

Adaptive Values

Return different values based on device:

final fontSize = adaptiveValue(
  mobile: 14.0,
  tablet: 16.0,
  desktop: 18.0,
);

final columns = adaptiveValue(
  mobile: 1,
  tablet: 2,
  desktop: 3,
);

Device Detection

Check device type anywhere in your code:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(
        isMobile ? 'Mobile' : isTablet ? 'Tablet' : 'Desktop'
      ),
    ),
    body: Column(
      children: [
        if (isMobile) const MobileWidget(),
        if (isTablet) const TabletWidget(),
        if (isDesktop) const DesktopWidget(),
      ],
    ),
  );
}

Using VizeLayout

Get screen info with constraints:

VizeLayout(
  builder: (context, info) {
    return Column(
      children: [
        Text('Device: ${info.device}'),
        Text('Orientation: ${info.orientation}'),
        Text('Screen: ${info.vizeScreenSize}'),
        Text('Widget: ${info.vizeWidgetSize}'),
      ],
    );
  },
)

Custom Breakpoints

Customize device detection breakpoints:

Vize.init(
  context,
  breakpoints: VizeBreakpoints(
    mobile: 600,   // Mobile < 600px
    tablet: 1024,   // Tablet 600-1024px, Desktop >= 1024px
  ),
);

Custom Figma Dimensions

Match your Figma design dimensions:

Vize.init(
  context,
  figmaWidth: 390,
  figmaHeight: 844,
);

User Font Scaling

Vize.init accepts a textScalar parameter — a multiplier applied on top of the responsive text scale returned by ts(). Use it to honour a user's font-size preference (small / medium / large) so that every .ts call across the entire app scales accordingly without wrapping the widget tree in an extra MediaQuery.

// Pass the user's chosen scale (e.g. 0.85, 1.0, or 1.15) on every build.
Vize.init(
  context,
  figmaWidth: 390,
  figmaHeight: 844,
  textScalar: userFontScale, // default: 1.0
);

Because Vize.init is called inside MaterialApp.builder on every rebuild, the new scalar takes effect immediately whenever the preference changes - no hot-restart required.

Core Methods

Method Description Example
Vize.init(context) Initialize Vize Vize.init(context)
w(percent) Width percentage w(50) - 50% width
h(percent) Height percentage h(30) - 30% height
ts(size) Scale text size ts(16) - scaled 16px
r(value) Scale radius r(12) - scaled 12px
pa(value) Padding all sides pa(16) - scaled padding
ps({h, v}) Symmetric padding ps(h: 20, v: 10)
po({l, t, r, b}) Individual padding po(l: 10, t: 20)
ws(percent) Width spacing ws(5) - 5% width
hs(percent) Height spacing hs(2) - 2% height
sp([step]) Standard spacing sp(2) - 16px scaled

Vize.init Parameters

Parameter Type Default Description
context BuildContext required Used to read MediaQuery screen dimensions
figmaWidth double? 390 The artboard width from your Figma design
figmaHeight double? 844 The artboard height from your Figma design
breakpoints VizeBreakpoints? VizeBreakpoints() Thresholds for mobile / tablet / desktop
textScalar double 1.0 Multiplier applied to all ts() values; use to honour a user font-size preference

Vize.I Methods

Access these via Vize.I:

Method Description
Vize.I.wp(percent) Width percentage
Vize.I.hp(percent) Height percentage
Vize.I.sw(value) Scale width from Figma
Vize.I.sh(value) Scale height from Figma
Vize.I.ts(size) Scale text size (respects textScalar)
Vize.I.r(value) Scale radius
Vize.I.pa(value) Padding all sides
Vize.I.ps({h, v}) Symmetric padding
Vize.I.po({l, t, r, b}) Individual padding
Vize.I.isMobile Check if mobile
Vize.I.isTablet Check if tablet
Vize.I.isDesktop Check if desktop
Vize.I.device Get device type
Vize.I.textScalar Current text scale multiplier

Device Flags

Flag Description
isMobile True if mobile device
isTablet True if tablet device
isDesktop True if desktop device

Extensions

All extensions work on numbers:

50.w        // Width percentage
30.h        // Height percentage
18.ts       // Text size scaling (respects textScalar)
12.r        // Radius scaling
16.pa       // Padding all sides
5.ws        // Width spacing
2.hs        // Height spacing
100.fws     // Scale width from Figma
50.fhs      // Scale height from Figma

Widgets

Widget Description
VizeBuilder Build different layouts per device
VizeWrapper Wrap with screen info
VizeLayout LayoutBuilder with VizeInfo

Models

VizeInfo properties:

  • orientation - Current orientation
  • device - Device type (mobile/tablet/desktop)
  • vizeScreen - Full screen size
  • vizeWidget - Local widget size
  • isPortrait - Portrait orientation check
  • isLandscape - Landscape orientation check
  • isMobile - Mobile device check
  • isTablet - Tablet device check
  • isDesktop - Desktop device check

Default Breakpoints

Device Width Range Default
Mobile < 600px < 600
Tablet 600-1024px 600-1024
Desktop >= 1024px >= 1024

Best Practices

  1. Initialize Early

    Vize.init(context); // In MaterialApp builder or root widget
    
  2. Use Helpers for Layouts: Prefer percentage-based for flexible containers.

  3. Use Figma Scaling for Components: Ensures pixel-perfect UI.

  4. Combine Approaches: Percentages for overall layout, Figma scaling for UI elements.

  5. Use Extensions: Makes code much cleaner and readable.

  6. Test on Multiple Devices: Small phones, tablets, and desktops.

  7. Pass textScalar on every init: Since Vize.init is called in MaterialApp.builder, pass your font-size preference on every build so the scalar stays in sync with the user's choice.

Complete Example

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (context, child) {
        Vize.init(
          context,
          figmaWidth: 390,
          figmaHeight: 844,
        );
        return child!;
      },
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        padding: 20.pa,
        child: Column(
          children: [
            Container(
              width: 100.w,
              height: 20.h,
              decoration: BoxDecoration(
                color: Colors.blueAccent,
                borderRadius: BorderRadius.circular(12.r),
              ),
              child: Center(
                child: Text(
                  'Header',
                  style: TextStyle(fontSize: 22.ts),
                ),
              ),
            ),
            2.hs,
            GridView.builder(
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              itemCount: 4,
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: adaptiveColumns(
                  mobile: 1,
                  tablet: 2,
                  desktop: 4,
                ),
                mainAxisSpacing: sp(2),
                crossAxisSpacing: sp(2),
              ),
              itemBuilder: (context, i) => Container(
                color: Colors.grey[200],
                alignment: Alignment.center,
                child: Text('Item $i'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Contributing

Contributions are very welcome! We'd love to see new features, bug fixes, documentation improvements, or anything that makes Vize better.

Local Development & Verification

To ensure consistency, we've included a "pre-flight" script. Before pushing your changes or opening a PR, please run the checks to verify formatting, analysis, and tests:

On Windows (PowerShell):

./check.ps1

On Linux/macOS:

chmod +x check.sh # Only needed the first time
./check.sh

How to Contribute

  1. Fork the repository
  2. Create a feature branch
    git checkout -b new-feature
    
  3. Commit your changes
    git commit -am 'Add new feature'
    
  4. Push to the branch
    git push origin new-feature
    
  5. Open a Pull Request on GitHub

Please make sure your code follows the existing style, includes tests where appropriate, and updates documentation if needed.

Thank you for helping improve Vize!

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

If you find Vize helpful, please give it a ⭐ on GitHub!

Contact


Made with ❤️ for the Flutter community

Libraries

vize