A composition-first, constraint-aware card layout engine for Flutter.
flux_card helps you build rich, reusable cards from a consistent set of primitives instead of
repeating one-off widget trees across your app.
It gives you:
- named content slots
- multiple layout modes
- overlays and underlays
- notch support
- built-in loading skeletons
- theme presets with
ThemeExtension - [NEW] zero-boilerplate
FluxCard.simple()factory - [NEW] expandable media and screen-reader semantics merging
Version 0.2.0 is the latest public release.
Most apps start with a simple card or two, then quickly end up with many variations:
- product cards
- article cards
- compact list cards
- promo banners
- profile cards
- ticket / coupon cards
- cards with badges, ribbons, overlays, and decorative backgrounds
- cards with loading states that should still match the final layout
At that point, teams usually either duplicate UI or build fragile ad-hoc abstractions.
flux_card gives you a structured middle ground:
- flexible enough for custom layouts
- consistent enough to scale across a design system
- focused enough to stay pleasant to use
FluxCardroot widget and zero-boilerplateFluxCard.simple()factory- Named slots:
media,header,body,footer - Layout modes:
column,row,responsive,inline - Expandable media support (
isMediaExpanded) for uniform grid heights FluxMedia,FluxSection, andFluxContenthelper widgetsFluxOverlayandFluxUnderlaywith slot targetingFluxOverlayBehavior.breakoutfor extruding overlays without disabling card clipping- Theme presets:
standard,compact,elevated,outlined - Loading support with
FluxCardSkeleton - Notch support with multiple notch styles
- Widgetbook examples and test coverage included
Add to your pubspec.yaml:
dependencies:
flux_card: ^0.2.0Then import:
import 'package:flux_card/flux_card.dart';For highly composed cards, use the default constructor with slot primitives:
FluxCard(
media: FluxMedia(
aspectRatio: 16 / 9,
child: Ink.image(
image: const NetworkImage('https://example.com/image.jpg'),
fit: BoxFit.cover,
),
),
header: const FluxSection(
title: Text('Card title'),
subtitle: Text('Supporting text'),
padding: EdgeInsets.zero,
),
body: const Text('Body content goes here.'),
footer: FluxSection.footer(
padding: EdgeInsets.zero,
actions:[
FilledButton(
onPressed: () {},
child: Text('Action'),
),
],
),
theme: FluxCardThemeData.elevated,
onTap: () {},
)If you just need a standard text-and-action card without the boilerplate, use the .simple factory:
FluxCard.simple(
title: 'Simple Card',
subtitle: 'Zero boilerplate',
description: 'A fast way to build standard cards.',
ctaLabel: 'Learn More',
onCtaPressed: () {},
media: FluxMedia.image(
aspectRatio: 16 / 9,
image: const NetworkImage('https://example.com/image.jpg'),
fit: BoxFit.cover,
),
theme: FluxCardThemeData.elevated,
)A FluxCard is built from four optional slots:
mediaheaderbodyfooter
Slots can contain any widget, but the package includes helper widgets for common patterns:
FluxMediaFluxSectionFluxContent
If a slot is null, it is omitted automatically.
Cards can have:
- underlays behind content
- overlays above content
This makes it easy to add:
- badges
- chips
- decorative gradients
- pricing ribbons
- texture / accent layers
- extruding promotional elements
You can switch between:
FluxLayoutMode.columnFluxLayoutMode.rowFluxLayoutMode.responsiveFluxLayoutMode.inline
FluxCardThemeData is a ThemeExtension, so cards can be themed:
- per card
- per screen
- app-wide
The root widget.
FluxCard({
FluxLayoutMode layout = FluxLayoutMode.column,
FluxMediaPosition mediaPosition = FluxMediaPosition.start,
FluxMediaSpan mediaSpan = FluxMediaSpan.all,
bool isMediaExpanded = false,
Widget? media,
Widget? header,
Widget? body,
Widget? footer,
List<Widget>? overlays,
List<Widget>? underlays,
Color? foregroundColor,
BoxDecoration? decoration,
FluxNotch? notch,
FluxDivider? divider,
double? width,
double? height,
bool fullWidth = false,
bool fullHeight = false,
FluxCardThemeData? theme,
Clip? clipBehavior,
String? semanticLabel,
bool? mergeSemantics,
VoidCallback? onTap,
VoidCallback? onLongPress,
bool loading = false,
Widget Function(BuildContext, Widget)? loadingWrapper,
})Use FluxCard when you want a reusable card surface that can scale from simple content cards to
highly composed promo or commerce layouts.
FluxMedia is a media-slot container for images or custom media content.
FluxMedia(
aspectRatio: 4 / 3,
child: Ink.image(
image: const NetworkImage(imageUrl),
fit: BoxFit.cover,
),
)Supports:
aspectRatio- explicit
width/height borderRadiuscolor/gradientforegroundColor/foregroundGradient
FluxSection is a structured section widget for header/footer style content.
const FluxSection(
title: Text('Product name'),
subtitle: Text('Short supporting text'),
padding: EdgeInsets.zero,
)Good for:
- title blocks
- metadata rows
- footer action areas
- structured content with leading / title / subtitle / trailing patterns
Also includes:
FluxSection.header(...)FluxSection.footer(...)
FluxContent is a flexible body wrapper.
Useful constructors:
FluxContent.column(...)FluxContent.row(...)FluxContent.wrap(...)
Use it for:
- grouped body text
- chip collections
- feature lists
- richer body layouts
FluxOverlay adds content above a selected slot or the whole card.
FluxOverlay(
targets: const {FluxTarget.media},
alignment: Alignment.topRight,
children: [
Chip(label: Text('New')),
],
)Use behavior to control how overlays are rendered:
FluxOverlayBehavior.containedkeeps the overlay inside the card layerFluxOverlayBehavior.breakoutallows the overlay to extend outside the card while the card itself remains clipped
Example:
FluxOverlay(
behavior: FluxOverlayBehavior.breakout,
targets: const {FluxTarget.media},
alignment: Alignment.bottomRight,
children:[
SizedBox(
width: 120,
height: 180,
child: Placeholder(),
),
],
)This is the recommended way to build extruding overlays. Note: Breakout overlays are rendering-optimized and will not cause unnecessary layout rebuilds.
FluxUnderlay adds decoration behind a slot or the whole card.
FluxUnderlay(
targets: const {FluxTarget.card},
decoration: BoxDecoration(
gradient: LinearGradient(
colors:[Color(0x11000000), Color(0x00000000)],
),
),
)Useful for:
- tinted surfaces
- subtle background gradients
- section accents
- decorative panels
FluxDivider inserts widgets between named slot boundaries.
FluxDivider(
afterHeader: Divider(height: 1),
)Useful for:
- ticket separators
- section separation
- visual rhythm between content blocks
FluxNotch adds shaped cutouts to the card outline.
FluxNotch.ticket(...)FluxNotch.ticketFree(...)FluxNotch.vShape(...)FluxNotch.vShapeFree(...)FluxNotch.slant(...)FluxNotch.slantFree(...)
Example:
FluxCard(
notch: const FluxNotch.ticket(
boundary: FluxSlotBoundary.afterHeader,
notchDepth: 14,
),
divider: const FluxDivider(
afterHeader: FluxDashedDivider(),
),
theme: FluxCardThemeData.outlined,
header: const Text('Concert Ticket'),
body: const Text('Gate opens at 7:00 PM'),
)The card outline is controlled by the card theme / shape.
FluxNotch controls notch geometry and placement only.
Built-in loading state that mirrors the card structure.
FluxCard(
loading: true,
media: FluxMedia(aspectRatio: 16 / 9, child: SizedBox()),
header: const FluxSection(
title: Text('Title'),
padding: EdgeInsets.zero,
),
body: const Text('Body'),
)Use FluxCardSkeleton when you want:
- a package-native loading state
- no extra dependency
- a skeleton that respects the slot layout of the final card
If your app already uses a dedicated loading package, bridge it with loadingWrapper.
FluxCard(
loading: isLoading,
loadingWrapper: (context, skeleton) {
return Skeletonizer(
enabled: true,
child: skeleton,
);
},
header: const FluxSection(
title: Text('Title'),
padding: EdgeInsets.zero,
),
)FluxCardThemeData controls the visual defaults for cards.
Built-in presets:
FluxCardThemeData.standardFluxCardThemeData.compactFluxCardThemeData.elevatedFluxCardThemeData.outlined
Per-card example:
final cardTheme = FluxCardThemeData.elevated.copyWith(
padding: const EdgeInsets.all(20),
spacing: 16,
);App-wide example:
MaterialApp(
theme: ThemeData(
extensions:[
FluxCardThemeData.elevated,
],
),
)Best for:
- product cards
- article cards
- promo banners
- profile cards
FluxCard(
layout: FluxLayoutMode.column,
...
)Best for:
- compact horizontal cards
- list rows
- side-by-side media/content cards
Tip for Fixed Widths: By default, Row mode distributes horizontal space via flexMedia and
flexContent ratios in the theme. If you need an exact pixel size for your image, just assign an
explicit width to FluxMedia(width: 120)—the layout engine will automatically respect it and
give all remaining flexible space to the content block!
FluxCard(
layout: FluxLayoutMode.row,
mediaPosition: FluxMediaPosition.start,
media: FluxMedia(
width: 120, // Forces an exact width, while content flexes to fill the rest
child: ...
),
...
)When placing cards inside a layout with fixed heights (like a GridView or an explicitly sized
parent container), you often want the media image to stretch and absorb all the remaining vertical
space so all cards look uniform.
FluxCard(
isMediaExpanded: true,
media: FluxMedia(
child: Ink.image(image: NetworkImage(url), fit: BoxFit.cover),
),
...
)(Note: Safety checks are built-in. If you put an expanded card inside an unbounded scrolling view
like ListView, the layout engine intelligently disables expansion to prevent Flutter crash
exceptions.)
Switches between column and row based on the card theme breakpoint.
FluxCard(
layout: FluxLayoutMode.responsive,
fullWidth: true,
...
)responsive works best when the card has a known width or uses fullWidth: true.
Useful for denser inline arrangements where the card behaves more like a structured content row.
FluxCard(
media: FluxMedia(
aspectRatio: 4 / 3,
child: Ink.image(
image: NetworkImage(imageUrl),
fit: BoxFit.cover,
),
),
overlays:[
FluxOverlay(
targets: const {FluxTarget.media},
alignment: Alignment.topLeft,
children:[
Chip(label: Text('Sale')),
],
),
],
header: const FluxSection(
title: Text('Product name'),
subtitle: Text('\$29.99'),
padding: EdgeInsets.zero,
),
footer: FluxSection.footer(
padding: EdgeInsets.zero,
actions:[
FilledButton(
onPressed: null,
child: Text('Add to cart'),
),
],
),
theme: FluxCardThemeData.elevated,
onTap: () {},
)FluxCard(
media: FluxMedia(
aspectRatio: 16 / 9,
child: Ink.image(
image: NetworkImage(imageUrl),
fit: BoxFit.cover,
),
),
overlays:[
FluxOverlay(
behavior: FluxOverlayBehavior.breakout,
targets: const {FluxTarget.media},
alignment: Alignment.bottomRight,
children:[
SizedBox(
width: 120,
height: 160,
child: Placeholder(),
),
],
),
],
header: const FluxSection(
title: Text('Breakout overlay'),
subtitle: Text('Promo style card'),
padding: EdgeInsets.zero,
),
theme: FluxCardThemeData.elevated,
)FluxCard(
notch: const FluxNotch.ticket(
boundary: FluxSlotBoundary.afterHeader,
notchDepth: 14,
),
divider: FluxDivider(
afterHeader: FluxDashedDivider(indent: 14, endIndent: 14),
),
theme: FluxCardThemeData.outlined,
header: const FluxSection(
title: Text('Concert Night'),
subtitle: Text('Saturday, 8:00 PM'),
padding: EdgeInsets.zero,
),
body: const Text('Gate opens at 7:00 PM. Row A, Seat 12.'),
)semanticLabellets you describe the card for assistive technologies- [NEW]
mergeSemantics: truecombines all inner text nodes so Screen Readers announce the card cohesively in one swipe. Can be set globally viaFluxCardThemeDataor per-card. - cards automatically expose button semantics when
onTaporonLongPressis provided foregroundColorhelps propagate a readable default foreground color through slot content
The package includes:
- a Widgetbook project for interactive exploration
- an example app
- use cases for cards, overlays, underlays, loading, themes, notches, and scrollable layouts
Recommended screenshot sections for this README:
- hero / marketing card
- advanced cards gallery
- notch styles
- breakout overlay example
0.2.0 introduces major developer experience and performance improvements:
- Simple Card Factory: Added
FluxCard.simple()to quickly build standard cards with zero boilerplate. - Expandable Media: Added
isMediaExpandedto dynamically stretch media insideGridViews without crashing unconstrained lists. - Exact Pixel Row Layouts:
FluxMatchHeightRownow perfectly respects explicit widths onFluxMedia(e.g.width: 120) while allocating remaining space to flex content. - Semantic Merging: Added
mergeSemanticsfor robust screen-reader accessibility. - Breakout Rendering Performance: Extracted breakout overlays into an isolated render tree to prevent entire-card rebuilds during geometry checks.
Possible future directions:
- more advanced card presets built on top of
FluxCard - more notch styles
- richer Widgetbook showcases
- higher-level convenience factories for common card patterns





