ui_framework_kit 1.1.3 copy "ui_framework_kit: ^1.1.3" to clipboard
ui_framework_kit: ^1.1.3 copied to clipboard

Production-ready Flutter UI kit: adaptive light/dark theming, form widgets, MCQ, shimmer skeletons, HTML/image renderers, and responsive utilities — all in one import.

ui_framework_kit #

pub.dev Flutter License: MIT Platform

A production-ready Flutter UI component library — adaptive light/dark theming, rich form widgets, MCQ, shimmer skeletons, image renderers, in-app WebView, YouTube player, link previews, and 20+ developer-friendly extensions. One import. Everything included.


Screenshots #

Home Typography Buttons
home typography buttons
Form Inputs Shimmer MCQ
forms shimmer mcq
Images and Avatars Newsfeed Carousel
images newsfeed carousel
YouTube Link Preview Web View
youtube link_preview webview
HTML Read More Theming
html read_more theme

All screenshots are generated by the flutter drive command documented in screenshots/README.md, so they stay in sync with the example app on every UI change.


Why ui_framework_kit? #

  • Zero boilerplate — every widget handles its own state, loading, errors, and theming internally.
  • Adaptive by default — colors, borders, and backgrounds automatically switch between light and dark mode.
  • Responsive out of the box — built on flutter_screenutil; use .w, .h, .sp sizing everywhere.
  • Beginner to advanced — copy-paste examples below get you running in minutes; deep configuration hooks let you customise everything.

Platform Support #

Android iOS Web macOS Windows Linux
Yes Yes Yes Yes Yes Yes

Quick Start (3 steps) #

Step 1 — Add the dependency #

# pubspec.yaml
dependencies:
  ui_framework_kit: ^1.0.0
flutter pub get

Step 2 — Wrap your app #

This is required. ScreenUtilInit makes all .w/.h/.sp sizing work correctly. Without it you will see layout errors.

import 'package:ui_framework_kit/ui_framework_kit.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      // Match your Figma / design canvas size.
      // 360×690 is a safe default if you have no design spec.
      designSize: const Size(360, 690),
      minTextAdapt: true,
      builder: (context, child) {
        return MaterialApp(
          theme: lightTheme,      // provided by ui_framework_kit
          darkTheme: darkTheme,   // provided by ui_framework_kit
          themeMode: ThemeMode.system,
          home: const HomePage(),
        );
      },
    );
  }
}

Step 3 — Use a widget #

import 'package:ui_framework_kit/ui_framework_kit.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          // Adaptive heading that respects light/dark mode
          TextWidget('Welcome!', textType: TextTypeConstants.heading),

          // Debounce-protected button with loading state
          InputButtonWidget(
            title: 'Get Started',
            onTap: () {},
            bgColor: AppColors.blueColor,
          ),
        ],
      ),
    );
  }
}

That is all the setup you need. The rest of this guide covers every widget in the library.


Widget Catalog #

Typography — TextWidget #

Typography preview

A single widget replaces Text, RichText, and all manual TextStyle work. It picks the right font size and weight automatically based on textType, and resolves the correct color for the current theme.

Semantic levels:

textType Typical use
TextTypeConstants.display Hero / splash titles
TextTypeConstants.heading Page headings (h1)
TextTypeConstants.title Section titles (h2)
TextTypeConstants.subTitle Card titles, list items
TextTypeConstants.normal Body text, captions
TextTypeConstants.custom Full manual control
// Semantic usage
TextWidget('Dashboard', textType: TextTypeConstants.heading)

TextWidget(
  'Last updated 5 min ago',
  textType: TextTypeConstants.normal,
  color: getColorByTheme(context: context, colorClass: AppColors.greyColor),
)

// Custom size + weight
TextWidget(
  'Featured',
  textType: TextTypeConstants.custom,
  textOptions: const TextOptions(fontSize: 11, letterSpacing: 1.4),
  fontWeight: FontWeight.w700,
  color: Colors.teal,
)

// Truncate with ellipsis
TextWidget(
  longString,
  textType: TextTypeConstants.subTitle,
  maxLines: 2,
  overflow: TextOverflow.ellipsis,
)

Buttons — InputButtonWidget #

Buttons preview

Handles filled and border variants, loading state, debounce (prevents double-taps), and optional SVG icon — all in one widget.

// Primary filled button
InputButtonWidget(
  title: 'Submit',
  onTap: _handleSubmit,
  bgColor: AppColors.blueColor,
)

// Outline / border button
InputButtonWidget(
  title: 'Cancel',
  isBorderButton: true,
  fontColor: AppColors.errorColor,
  bgColor: AppColors.errorColor,
  onTap: () => Navigator.pop(context),
)

// Loading state — disables taps and shows a spinner
InputButtonWidget(
  title: 'Saving…',
  isLoading: _isSaving,
  onTap: _save,
)

// Debounce — prevents accidental double-tap (default 500 ms)
InputButtonWidget(
  title: 'Pay Now',
  onTap: _processPayment,
  debounceDuration: const Duration(milliseconds: 800),
)

// With SVG icon
InputButtonWidget(
  title: 'Upload',
  asset: 'assets/icons/upload.svg',
  assetSize: 20,
  onTap: _pickFile,
)

Key properties:

Property Type Default Description
title String required Button label
onTap VoidCallback? Tap handler
isLoading bool false Shows spinner, blocks taps
isBorderButton bool false Outline style
bgColor ColorModel? Background / border color
fontColor ColorModel? Label color
debounceDuration Duration 500ms Debounce window
height / width double? Override size

Forms #

Form inputs preview

All form widgets share a consistent API: labelText, hintText, validator, onChanged, and onSaved. They work inside a standard Flutter Form widget with a GlobalKey<FormState>.

Text Field — TextFieldWidget

// Simple text input
TextFieldWidget(
  labelText: 'Full Name',
  hintText: 'Enter your name',
  onChanged: (value) => _name = value ?? '',
)

// Password field — toggles show/hide automatically
TextFieldWidget(
  labelText: 'Password',
  isPassword: true,
  onChanged: (value) {},
)

// Multiline notes field
TextFieldWidget(
  labelText: 'Notes',
  maxLines: 5,
  minLines: 3,
  keyboardType: TextInputType.multiline,
  onChanged: (value) {},
)

// Read-only display field
TextFieldWidget(
  labelText: 'Email (cannot edit)',
  initialValue: user.email,
  readOnly: true,
  onChanged: (value) {},
)

// With validation inside a Form
Form(
  key: _formKey,
  child: TextFieldWidget(
    labelText: 'Email',
    keyboardType: TextInputType.emailAddress,
    validator: (v) => (v?.contains('@') ?? false) ? null : 'Invalid email',
    onChanged: (v) => _email = v ?? '',
    onSaved: (v) => _email = v ?? '',
  ),
)

Date Picker — DatePickerTextField

Uses Flutter's built-in showDatePicker by default. Inject any calendar system (Nepali BS, Persian, Hijri) via customPickerBuilder.

// Standard Gregorian picker
DatePickerTextField(
  labelText: 'Date of Birth',
  firstDate: DateTime(1950),
  lastDate: DateTime.now(),
  dateFormatter: (d) => '${d.day}/${d.month}/${d.year}',
  onChanged: (value) => print(value),
)

// Custom calendar (e.g. Nepali BS)
DatePickerTextField(
  labelText: 'जन्म मिति',
  customPickerBuilder: (ctx, currentDate) async {
    // Use any Nepali/custom calendar package here.
    // Return a standard Dart DateTime (or null to cancel).
    return await myNepaliPicker(context: ctx, initial: currentDate);
  },
  onChanged: (value) {},
)

Time Picker — TimePickerTextField

TimePickerTextField(
  labelText: 'Appointment Time',
  initialTime: const TimeOfDay(hour: 9, minute: 0),
  timeFormatter: (t) => t.format(context),   // e.g. "9:00 AM"
  onChanged: (value) => print(value),
)

Generic — works with any type. Provide an itemToString mapper when T is not String.

// String list
DropdownWidget<String>(
  hintText: 'Select country',
  itemList: ['Nepal', 'India', 'USA', 'UK'],
  showSelected: true,
  onPressed: (country) => setState(() => _country = country),
)

// Custom object
DropdownWidget<Category>(
  hintText: 'Select category',
  itemList: categories,
  itemToString: (cat) => cat.name,          // how to display each item
  onPressed: (cat) => setState(() => _cat = cat),
)

Multi-Select Dropdown — MultiSelectDropDownWidget

MultiSelectDropDownWidget<String>(
  hintText: 'Select skills',
  itemList: ['Flutter', 'Dart', 'Firebase', 'REST', 'GraphQL'],
  onChanged: (selected) => setState(() => _skills = selected ?? []),
)

Checkbox — CheckBoxWidget

// With label
CheckBoxWidget(
  title: 'I agree to the Terms & Conditions',
  isSelected: _agreed,
  onChanged: (val) => setState(() => _agreed = val ?? false),
)

// Without label (icon only)
CheckBoxWidget(
  isSelected: item.isSelected,
  onChanged: (val) => _toggle(item),
)

Switch — SwitchWidget

SwitchWidget(
  isSelected: _notificationsEnabled,
  onSelected: (val) => setState(() => _notificationsEnabled = val ?? false),
)

Search Field — SearchTextFieldWidget

SearchTextFieldWidget(
  hintText: 'Search products…',
  onChanged: (query) => _filter(query),
)

MCQ — McqWidget #

MCQ widget preview

Built for quiz and assessment UIs. Manages correct/incorrect/selected state internally. Wire up selectedAnswerCallback to your own state.

McqWidget(
  mcqConfig: MCQConfig(
    id: 'q1',
    questionNoToDisplay: 1,
    question: 'Which of the following is a compiled language?',
    answers: [
      MCQAnswerConfig(id: 'a', answer: 'Python'),
      MCQAnswerConfig(id: 'b', answer: 'JavaScript'),
      MCQAnswerConfig(id: 'c', answer: 'Go', isCorrectAnswer: true),
      MCQAnswerConfig(id: 'd', answer: 'Ruby'),
    ],
  ),
  selectedAnswerCallback: (questionId, answer, index) {
    setState(() => _answers[questionId] = answer?.id);
  },
)

Show the answer (review mode):

// Pass the ID of the answer the user chose
McqWidget(
  mcqConfig: MCQConfig(
    id: 'q1',
    question: '…',
    answers: [...],
    selectedAnswerId: _answers['q1'],   // highlights selected + marks correct
  ),
  selectedAnswerCallback: (_, __, ___) {},
)

Shimmer Skeletons — ShimmerWidget #

Shimmer skeleton preview

Drop these in while data is loading. Seven presets cover the most common loading patterns.

// While loading, show skeleton:
isLoading
  ? ShimmerWidget(shimmerType: ShimmerType.listItemInfo)
  : MyRealWidget()
shimmerType Looks like
ShimmerType.basicCard Plain card rectangle
ShimmerType.cardInfo Card with title + subtitle lines
ShimmerType.basicListItem Single-line list row
ShimmerType.listItemInfo List row with avatar + two lines
ShimmerType.callListItem Call log row (avatar + name + time)
ShimmerType.authorInfo Author tile (avatar + name + desc)
ShimmerType.messageList Chat bubble row

Show a full list skeleton:

DefaultListShimmerWidget(
  shimmerType: ShimmerType.listItemInfo,
  itemCount: 6,
)

Images & Avatars #

Images and avatars preview

Network image with cache

CachedNetworkImageWidget(
  imageUrl: 'https://example.com/photo.jpg',
  fit: BoxFit.cover,
  height: 200,
  width: double.infinity,
)

Circular avatar with initials fallback

If the image URL is empty or fails to load, the widget automatically shows the user's initials on a coloured background — no extra code needed.

CircularCachedNetworkImage(
  imageUrl: user.photoUrl,    // can be empty string
  fallBackString: user.name,  // "Alice Johnson" renders "AJ"
  size: 44,
)

SVG image

SvgImageRenderWidget(
  assetPath: 'assets/icons/logo.svg',
  width: 48,
  height: 48,
)

Newsfeed / Author Tile — AuthorListTileWidget #

Newsfeed/author tile preview

A flexible tile widget for social posts, news articles, comment rows, contact lists, and chat lists. Compose it with AuthorListTileModel.

// Social post header
AuthorListTileWidget(
  authorListTileModel: AuthorListTileModel(
    authorImageConfig: AuthorImageConfig(
      imageUrl: user.avatarUrl,
      isToDisplayAuthor: true,
      imageSize: 44,
    ),
    titleConfig: AuthorTitleConfig(
      title: user.name,
      titleFontWeight: FontWeight.w700,
      suffixWidget: verified
          ? const Icon(Icons.verified, color: Colors.blue, size: 15)
          : null,
    ),
    descConfig: AuthorTitleConfig(
      title: '2 hours ago, Public',
      titleFontColor: AppColors.greyColor,
    ),
    suffixWidget: IconButton(
      icon: const Icon(Icons.more_horiz),
      onPressed: () {},
    ),
  ),
)

Common patterns:

Pattern How to build it
Text post Header tile + TextWidget below
Image post Header tile + CachedNetworkImageWidget below
Multi-image grid Header tile + Row of images below
Link preview post Header tile + LinkPreviewWidget below
News article Tile with thumbnail in suffixWidget + category badge
Comment row Small avatar (imageSize: 36) + suffixWidget for like count
Contact list removeAuthorImagePadding: true + call/message icons in suffix
Chat list Unread badge in suffix + timestamp in titleConfig.suffixWidget

bottomWidget — renders inside the tile's body column (good for inline badges or tags):

AuthorListTileWidget(
  authorListTileModel: AuthorListTileModel(
    titleConfig: AuthorTitleConfig(title: 'Alice'),
    descConfig: AuthorTitleConfig(title: 'Replied to your comment'),
    bottomWidget: Row(
      children: [
        Chip(label: Text('Flutter')),
        Chip(label: Text('Dart')),
      ],
    ),
  ),
)

WebView — WebViewWidget #

WebView preview

An in-app browser powered by flutter_inappwebview. Shows a progress bar while loading and an error state on failure.

// Minimal
WebViewWidget(url: 'https://flutter.dev')

// Full control
WebViewWidget(
  url: 'https://my-app.com/terms',
  showProgressBar: true,
  progressBarColor: AppColors.blueColor,
  onPageFinished: () => print('loaded'),
  onLoadError: (url, message) => print('error: $message'),
)

Android: add INTERNET permission to AndroidManifest.xml. iOS: add NSAppTransportSecurity to Info.plist if loading http URLs.


YouTube Player — YoutubePlayerWidget #

YouTube player preview

Embeds a YouTube video by its video ID using youtube_player_iframe.

YoutubePlayerWidget(
  videoId: 'dQw4w9WgXcQ',   // the part after ?v= in the YouTube URL
)

Link preview

Fetches open-graph metadata and renders a rich card. Tapping launches the URL.

LinkPreviewWidget(
  link: 'https://flutter.dev/multi-platform',
  width: double.infinity,
)

HTML Rendering — RenderHtmlWidget #

HTML rendering preview

Renders a subset of HTML tags inside a Flutter widget tree. Useful for CMS content, rich text from APIs, or inline HTML strings.

RenderHtmlWidget(
  htmlContent: '''
    <h2>Getting Started</h2>
    <p>This is <strong>bold</strong> and <em>italic</em> text.</p>
    <ul>
      <li>Item one</li>
      <li>Item two</li>
    </ul>
  ''',
)

Toast Notifications #

// Success
context.showToastMessage(
  toastEnum: ToastEnum.success,
  toastMessage: 'Profile saved!',
);

// Error
context.showToastMessage(
  toastEnum: ToastEnum.error,
  toastMessage: 'Something went wrong. Please try again.',
);

// Info
context.showToastMessage(
  toastEnum: ToastEnum.info,
  toastMessage: 'New version available.',
);

// Warning
context.showToastMessage(
  toastEnum: ToastEnum.warning,
  toastMessage: 'You are offline.',
);

Dynamic Message Banner — DynamicMessageBannerWidget #

Dynamic message banner / carousel preview

An auto-playing image carousel with dot indicators. Pass one URL for a single image (no swipe), or several to enable autoplay + indicators. Autoplay pauses automatically when the user has Reduce Motion enabled.

DynamicMessageBannerWidget(
  imageUrls: const [
    'https://example.com/banner1.png',
    'https://example.com/banner2.png',
    'https://example.com/banner3.png',
  ],
  placeHolderImage: 'assets/icons/image_placeholder.svg',
)

Read More Text — ReadMoreText #

Read more text preview

Truncates a long string with a tappable "read more / read less" toggle. Use TrimMode.Length for character-based trimming or TrimMode.Line for line-based trimming.

// Trim at a fixed character count
ReadMoreText(
  longBio,
  trimMode: TrimMode.Length,
  trimLength: 240,
  trimCollapsedText: ' read more',
  trimExpandedText: ' read less',
  colorClickableText:
      getColorByTheme(context: context, colorClass: AppColors.blueColor),
)

// Trim at two lines instead
ReadMoreText(
  longBio,
  trimMode: TrimMode.Line,
  trimLines: 2,
)

Empty State — EmptyStateWidget #

A drop-in placeholder for "no data yet", "no search results", or terminal error screens. Optional SVG illustration, optional CTA button, theme-aware.

EmptyStateWidget(
  title: 'No transactions',
  description: 'When you make a payment it will appear here.',
  svgAsset: 'assets/illustrations/empty_wallet.svg',
  actionLabel: 'Add money',
  onAction: _openAddMoney,
)

An in-flow status strip with severity-coloured icon, title, message, and optional dismiss button. Different from DynamicMessageWidget (which is content-card oriented) — BannerWidget is the "you are offline" / "saved successfully" header style.

BannerWidget(
  severity: BannerSeverity.warning,
  title: 'Offline',
  message: 'Changes will sync when you reconnect.',
  isDismissible: true,
  onDismiss: _hideOfflineBanner,
)

BannerSeverity values: info, warning, error, success.


Async builder — AsyncBuilder<T> #

Wraps a Future<T> and swaps automatically between shimmer loading, success, and error states. Replaces hand-written FutureBuilder blocks.

AsyncBuilder<List<Tx>>(
  future: api.fetchTransactions(),
  builder: (context, txs) => TxList(txs),
  onRetry: () => setState(() {}),               // re-runs the future
  shimmerType: ShimmerType.listItemInfo,        // skeleton variant
  errorMessage: 'Could not load transactions.',
)

Provide errorBuilder: and/or loadingBuilder: to fully replace the defaults.


Retryable error — RetryableErrorWidget #

Standalone error block with icon, message, and a retry button. Used by AsyncBuilder internally and useful directly when you need a manual error UI.

RetryableErrorWidget(
  title: 'Network error',
  message: 'We could not reach the server.',
  onRetry: _refresh,
)

Confirmation dialog — ConfirmDialogWidget #

A theme-aware yes/no/destructive dialog. Use the static show helper to await the user's choice.

final confirmed = await ConfirmDialogWidget.show(
  context,
  title: 'Delete account?',
  message: 'This cannot be undone.',
  confirmLabel: 'Delete',
  destructive: true,
);
if (confirmed == true) _deleteAccount();

destructive: true paints the confirm button in the error tint; destructive: false (default) uses the primary blue.


OTP / PIN input — OtpInputWidget #

A row of single-digit input boxes with auto-advance, paste-to-fill, backspace navigation, and digit-only filtering.

OtpInputWidget(
  length: 6,
  autoFocus: true,
  onChanged: (v) {},
  onCompleted: (code) => _verify(code),
)

// PIN entry — same widget, obscured
OtpInputWidget(
  length: 4,
  obscureText: true,
  onCompleted: _unlock,
)

Theme tokens — AppSpacing, AppRadius #

Avoid raw 4/8/12 literals scattered across your layout — pick a token and stay consistent with the rest of the kit.

Padding(padding: EdgeInsets.all(AppSpacing.md.w))   // 12
SizedBox(height: AppSpacing.lg.h)                    // 16
Container(
  decoration: BoxDecoration(
    borderRadius: BorderRadius.circular(AppRadius.lg.r), // 12
  ),
)
// or use the pre-built BorderRadius value:
BorderRadius radius = AppRadius.mdRadius;  // 8

Spacing: xxs (2), xs (4), sm (8), md (12), lg (16), xl (24), xxl (32). Radius: sm (4), md (8), lg (12), xl (16), full (pill / circle).


Theming #

Adaptive color tokens preview

lightTheme and darkTheme are fully configured ThemeData objects with Material 3 enabled. They include custom styles for DatePickerTheme, TimePickerTheme, BottomNavigationBarTheme, and NavigationBarTheme.

MaterialApp(
  theme: lightTheme,
  darkTheme: darkTheme,
  themeMode: ThemeMode.system,  // follows device setting
)

Extend without replacing:

theme: lightTheme.copyWith(
  colorScheme: lightTheme.colorScheme.copyWith(
    primary: Colors.teal,
  ),
),

Adaptive colors #

AppColors provides semantic color tokens. Use getColorByTheme to resolve the correct shade at runtime:

// In a widget build method:
final primary = getColorByTheme(
  context: context,
  colorClass: AppColors.primaryColor,
);

Container(color: primary)

Available tokens: primaryColor, blueColor, blackColor, whiteColor, greyColor, lightGreyColor, backgroundColor, borderColor, errorColor, successColor, warningColor, infoColor.


Accessibility #

ui_framework_kit ships WCAG 2.2 AA-aligned defaults out of the box, all dynamically configurable. No code changes are required to get the basics — but every behaviour can be overridden globally or per-widget.

What's wired in by default #

Area Behaviour Override
Screen reader labels Every interactive widget exposes a Semantics node (button, checkbox, switch, link, etc.) with a label sourced from the visible title / answer / link. Pass semanticLabel: / semanticHint: per widget.
Touch target ≥ 48 dp InputButtonWidget, CheckBoxWidget, search clear, and dismiss buttons enforce a minimum height of 48 logical px. UiFrameworkConfig.configure(accessibility: AccessibilityConfig(minTouchTargetSize: 56)).
Text scaling TextWidget honours MediaQuery.textScalerOf(context) — labels grow with the user's accessibility setting. Pass a manual textScaler: to a single widget if needed.
Reduce motion DynamicMessageBannerWidget pauses autoplay and ShimmerWidget freezes when MediaQuery.disableAnimations is set. AccessibilityConfig(respectReduceMotion: false) to opt out.
Color-blind safe state The MCQ widget renders a tick/cross icon next to the green/red state colour, so the result is not communicated through color alone. AccessibilityConfig(addStatusIconsBesideColor: false) to disable.
Localised action labels Defaults are English (Clear, Dismiss, Play video, correct, incorrect). Pass localized strings via AccessibilityConfig.

Configure once, globally #

void main() {
  UiFrameworkConfig.configure(
    accessibility: const AccessibilityConfig(
      // Localize the built-in tooltips / SR strings:
      clearActionLabel: 'मेटाउनुहोस्',
      dismissActionLabel: 'बन्द गर्नुहोस्',
      playVideoLabel: 'भिडियो चलाउनुहोस्',
      correctAnswerSemanticValue: 'सही',
      incorrectAnswerSemanticValue: 'गलत',
      // Bump touch targets for a senior-friendly app:
      minTouchTargetSize: 56,
    ),
  );
  runApp(const MyApp());
}

Per-widget overrides #

InputButtonWidget(
  title: 'Pay',
  onTap: _pay,
  semanticLabel: 'Pay invoice',           // overrides the visible title for SR
  semanticHint: 'Charges your saved card', // additional context after the label
)

CheckBoxWidget(
  isSelected: agreed,
  title: 'I agree to the terms',
  semanticLabel: 'I agree to the terms and conditions',
  onChanged: (v) => setState(() => agreed = v),
)

LinkPreviewWidget(
  link: url,
  semanticLabel: 'Open Flutter homepage', // friendlier than the raw URL
)

Read live system settings inside your own widgets #

The AccessibilityContext extension exposes the same MediaQuery signals the kit uses:

@override
Widget build(BuildContext context) {
  if (context.prefersReducedMotion) {
    // Render a static frame instead of an animation
  }
  if (context.prefersBoldText) { /* ... */ }
  if (context.usesAccessibleNavigation) { /* ... */ }
  if (context.prefersHighContrast) { /* ... */ }

  final scaledFontSize = context.textScaler.scale(14);

  // Read the current AccessibilityConfig
  final touchTarget = context.a11yKit.minTouchTargetSize;
  return SizedBox(...);
}

Checklist for accessible apps #

  • Wrap your app in MaterialApp (not just WidgetsApp) so MediaQuery.textScalerOf and MediaQuery.disableAnimationsOf are populated by the platform.
  • Test with TalkBack on Android and VoiceOver on iOS — every button, switch, checkbox, and MCQ answer should be announced with state.
  • Use system text size at 200% to confirm your layouts wrap rather than clip.
  • Run with Reduce Motion enabled in OS accessibility settings — banners should pause, shimmer should freeze.
  • Leave addStatusIconsBesideColor: true (the default) so MCQ answers carry both colour and an icon.
  • For decorative images, use ExcludeSemantics to keep them out of the SR's reading order.

Global Font Configuration #

Override the default Poppins font before runApp:

void main() {
  // Option A — use any font registered in your pubspec assets
  UiFrameworkConfig.configure(fontFamily: 'Nunito');

  // Option B — full control via a factory function
  UiFrameworkConfig.configure(
    fontStyleBuilder: ({required fontSize, fontWeight, color, height, letterSpacing, fontFamily}) {
      return GoogleFonts.inter(
        textStyle: TextStyle(
          fontSize: fontSize,
          fontWeight: fontWeight,
          color: color,
          height: height,
          letterSpacing: letterSpacing,
        ),
      );
    },
  );

  runApp(const MyApp());
}

Resolution priority (highest first):

  1. Per-widget fontFamily in TextOptions
  2. UiFrameworkConfig.fontStyleBuilder (full factory)
  3. UiFrameworkConfig.fontFamily (string name)
  4. Google Fonts Poppins (built-in default)

Extensions Cheat Sheet #

Widget extensions #

myWidget.center()                    // wraps in Center
myWidget.padAll(value: 16)           // EdgeInsets.all(16)
myWidget.padHorizontal(horizontal: 20)
myWidget.padVertical(vertical: 12)
myWidget.padLeft(left: 8)
myWidget.padRight(right: 8)
myWidget.padTop(top: 4)
myWidget.padBottom(bottom: 4)
myWidget.visible(isLoggedIn)         // hides widget when false
myWidget.onTap(() => doSomething())  // wraps in InkWell

Spacing (inside Column / Row) #

16.verticalSpace     // SizedBox(height: 16.h)
12.horizontalSpace   // SizedBox(width: 12.w)

BuildContext extensions #

context.isDark                  // true when dark mode is active
context.theme                   // Theme.of(context)
context.textTheme               // Theme.of(context).textTheme
context.statusBarHeight         // top safe area height
context.navigationBarHeight     // bottom safe area height
context.showToastMessage(...)   // show a toast

String extensions #

'hello world'.toTitleCase()          // "Hello World"
'hello'.toCapitalized()              // "Hello"
'Alice Johnson'.firstName()          // "Alice"
'Alice Johnson'.firstTwoLetters()    // "Al"
'Alice Johnson'.firstLetter()        // "A"

DateTime extensions #

date.isToday      // bool
date.isYesterday  // bool
date.isTomorrow   // bool

getTimeAgo('Mon, Apr 22, 2024 5:26 PM')  // "3 days ago"
parseDate('2024-04-22')                   // "Mon, Apr 22, 2024"
parseYearMonthDay(dateTime)               // "2024-04-22"

int extensions #

1.toBool()   // true
0.toBool()   // false

Common Mistakes #

1. Forgot ScreenUtilInit

FlutterError: No ScreenUtil found in context

Wrap your MaterialApp with ScreenUtilInit as shown in the Quick Start above.


2. Using .w/.h outside of a widget build

ScreenUtil sizing only works after the ScreenUtilInit tree is built. Do not call .w/.h in main() or global constants.

// Wrong (crashes — runs before ScreenUtilInit's tree is built)
final double size = 40.w;

// Correct (inside build, after ScreenUtilInit is in scope)
Widget build(BuildContext context) {
  return SizedBox(width: 40.w, height: 40.h);
}

3. TextFieldWidget with both initialValue and controller

AssertionError: IF value has been set on initialValue then controller value must be null

Use one or the other, never both.

// Pass a controller (and let it own the text)
TextFieldWidget(controller: _controller, onChanged: (v) {})

// Or pass an initialValue (no controller)
TextFieldWidget(initialValue: 'prefilled', onChanged: (v) {})

4. WebView on Android — blank screen

Add the internet permission to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />

5. Images not loading in release build

Ensure cached_network_image cache is not corrupted. Call PaintingBinding.instance.imageCache.clear() if needed. For https-only issues on iOS check NSAppTransportSecurity in Info.plist.


Running the Example App #

A full showcase app covering every widget is included in the example/ folder.

cd example
flutter pub get
flutter run

The example covers: Typography, Buttons, all form widgets, Shimmer skeletons, MCQ, Images and Avatars, Newsfeed patterns, Carousel, YouTube player, Link preview, WebView, HTML rendering, Read More Text, and Theming and Colors.