omni_pixel 0.1.0 copy "omni_pixel: ^0.1.0" to clipboard
omni_pixel: ^0.1.0 copied to clipboard

Figma-first responsive sizing for Flutter. Register the device frames your designers use, then write 48.opx to get pixel-perfect scaling on any screen.

omni_pixel #

Figma-first responsive sizing for Flutter. Register the device frames your designers use in Figma, then write the raw Figma numbers in your code — 48.opx — and get pixel-perfect scaling on every screen.

The idea #

Designers spec a screen on a concrete frame (say, an iPhone 14 at 390 × 844). Developers then eyeball those values onto dozens of real devices. omni_pixel closes that gap: you tell it which frames the design was made on, and .opx converts every design value to the current device using the longest side of the screen:

value.opx = value × (screen.longestSide / designDevice.longestSide) × multiplier

Using the longest side means portrait and landscape produce the same .opx value, and tall or short screens scale from the axis that actually differs the most between devices. The multiplier is an optional correction you define per aspect-ratio range and platform.

Quick start #

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // OmniBuilder in MaterialApp.builder covers every route.
      builder: (context, child) => OmniBuilder(
        devices: const [
          OmniDevices.iphone13And14,  // 390 x 844 — the frame in Figma
          OmniDevices.androidCompact, // 412 x 917
          OmniDevices.ipadPro11,      // 834 x 1194 — the tablet frame
        ],
        multipliers: const [
          OmniMultiplier.above(2.2, 0.97, platform: OmniPlatform.android),
          OmniMultiplier.below(1.6, 1.05), // squarish screens, any platform
        ],
        child: child!,
      ),
      home: const HomePage(),
    );
  }
}

Then anywhere in the app, write the numbers exactly as they appear in Figma:

Container(
  width: 343.opx,
  height: 220.opx,
  padding: EdgeInsets.all(20.opx),
  decoration: BoxDecoration(borderRadius: BorderRadius.circular(16.opx)),
  child: SizedBox(height: 48.opx, child: FilledButton(...)), // 48 px in Figma
)

How the design device is chosen #

You can register several frames (a phone frame and a tablet frame, for example). On every rebuild OmniPixel picks the best one for the current screen:

  1. Devices whose platform matches the running platform are preferred. If none match, platform-agnostic devices (platform: null) are used; failing that, all devices are considered.
  2. Within that pool, the device with the aspect ratio closest to the screen's wins.
  3. Ties are broken by the closest longest side.

So on an iPad your iPad frame drives the scaling, and on a phone your phone frame does — automatically. Force a specific frame with OmniBuilder(designDeviceOverride: ...) when you want to opt out of selection.

OmniDevices ships presets for the full Figma frame panel (iPhone SE through 17, Android Compact/Medium/Expanded, iPads, Surfaces, MacBooks, Desktop, TV, Apple Watches — plus the legacy presets), and any custom OmniDevice(name:, width:, height:, platform:) works.

Multipliers #

Multipliers let you tune scaling for families of screens instead of hardcoding if/else chains:

multipliers: const [
  // Rules are matched against the orientation-independent aspect ratio
  // (longestSide / shortestSide, always >= 1).
  OmniMultiplier.between(2.04, 2.16, 1.02),                        // any platform
  OmniMultiplier.above(2.16, 1.05, platform: OmniPlatform.android), // tall Androids
  OmniMultiplier.below(1.6, 0.95, platform: OmniPlatform.ios),      // iPads
]

Rules are min-inclusive and max-exclusive, so adjacent ranges never double-match at a boundary. Platform-specific rules take precedence over platform-agnostic ones; within each group the first match in list order wins; if nothing matches, the multiplier is 1.0. The resolved multiplier applies to .opx, .h and .w (use .dh/.dw when you want raw percentages untouched by your rules).

All helpers #

Helper Result
48.opx 48 Figma px → device px (scale × multiplier)
10.h 10% of screen height × multiplier
10.w 10% of screen width × multiplier
10.dh 10% of screen height (raw)
10.dw 10% of screen width (raw)
10.ls 10% of the longest side (raw, orientation-stable)
10.ss 10% of the shortest side (raw, orientation-stable)

Globals for parity with classic sizer packages: deviceWidth, deviceHeight, deviceAspectRatio (longest / shortest).

Introspection for debug overlays: OmniPixel.instance.designDevice, .scale, .multiplier, .platform, .aspectRatio, .isPortrait, and OmniPixel.instance.debugDescription().

Without OmniBuilder #

OmniBuilder is just a thin wrapper. You can drive the engine yourself:

OmniPixel.init(context, devices: const [OmniDevices.iphone13And14]); // needs MediaQuery
OmniPixel.update(context); // re-run with the stored configuration

Testing your layouts #

OmniPixel.configure needs no BuildContext, which makes unit tests trivial:

OmniPixel.configure(
  size: const Size(430, 932),
  devices: const [OmniDevices.iphone13And14],
);
expect(48.opx, closeTo(48 * 932 / 844, 1e-9));

OmniPixel.debugPlatformOverride forces a platform, and OmniPixel.reset() returns to the uninitialized state between tests. See test/omni_pixel_test.dart for full examples, including widget tests that resize tester.view.

Notes and gotchas #

  • Fonts: fontSize: 16.opx works, and Flutter's user text scaling (TextScaler) still applies on top of it — that's usually what you want for accessibility.
  • Web and desktop: platform detection uses kIsWeb + defaultTargetPlatform, never dart:io, so the package runs on all Flutter targets. Window resizes and split-screen changes re-trigger OmniBuilder automatically.
  • Name clashes: other sizer packages also define .h/.w. Hide one side's extension with import ... hide OmniPixelNum; (or hide theirs) in files that import both.
  • Fractional pixels: .opx returns unrounded doubles; Flutter handles sub-pixel values fine. Clamp when you need bounds: 48.opx.clamp(44.0, 56.0).
3
likes
0
points
156
downloads

Publisher

unverified uploader

Weekly Downloads

Figma-first responsive sizing for Flutter. Register the device frames your designers use, then write 48.opx to get pixel-perfect scaling on any screen.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on omni_pixel