ui_framework_kit 1.1.4
ui_framework_kit: ^1.1.4 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 #
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 |
|---|---|---|
![]() |
![]() |
![]() |
| Form Inputs | Shimmer | MCQ |
|---|---|---|
![]() |
![]() |
![]() |
| Images and Avatars | Newsfeed | Carousel |
|---|---|---|
![]() |
![]() |
![]() |
| YouTube | Link Preview | Web View |
|---|---|---|
![]() |
![]() |
![]() |
| HTML | Read More | Theming |
|---|---|---|
![]() |
![]() |
![]() |
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,.spsizing 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 #

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 #

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 #

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),
)
Dropdown — DropdownWidget<T>
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 #

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 #

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 #

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 #

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 #

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
INTERNETpermission toAndroidManifest.xml. iOS: addNSAppTransportSecuritytoInfo.plistif loading http URLs.
YouTube Player — YoutubePlayerWidget #

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 — LinkPreviewWidget #

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 #

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 #

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 #

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,
)
Banner — BannerWidget #
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 #

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 justWidgetsApp) soMediaQuery.textScalerOfandMediaQuery.disableAnimationsOfare 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
ExcludeSemanticsto 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):
- Per-widget
fontFamilyinTextOptions UiFrameworkConfig.fontStyleBuilder(full factory)UiFrameworkConfig.fontFamily(string name)- 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.

