Flux Card
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 NEWzero-boilerplateFluxCard.simple()factoryNEWexpandable media and screen-reader semantics merging
Version 0.2.0 is the latest public release.
Preview
Why use flux_card?
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
Features
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
Installation
Add to your pubspec.yaml:
dependencies:
flux_card: ^0.2.0
Then import:
import 'package:flux_card/flux_card.dart';
Quick start
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,
)
Core ideas
1. Slots
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.
2. Layers
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
3. Layout modes
You can switch between:
FluxLayoutMode.columnFluxLayoutMode.rowFluxLayoutMode.responsiveFluxLayoutMode.inline
4. Theme
FluxCardThemeData is a ThemeExtension, so cards can be themed:
- per card
- per screen
- app-wide
Widget overview
FluxCard
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
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
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
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
FluxOverlay adds content above a selected slot or the whole card.
FluxOverlay(
targets: const {FluxTarget.media},
alignment: Alignment.topRight,
children: [
Chip(label: Text('New')),
],
)
Overlay behaviors
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
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
FluxDivider inserts widgets between named slot boundaries.
FluxDivider(
afterHeader: Divider(height: 1),
)
Useful for:
- ticket separators
- section separation
- visual rhythm between content blocks
FluxNotch
FluxNotch adds shaped cutouts to the card outline.
Supported styles
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'),
)
Important
The card outline is controlled by the card theme / shape.
FluxNotch controls notch geometry and placement only.
FluxCardSkeleton
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'),
)
When to use it
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,
),
)
Theming
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,
],
),
)
Layout modes
Column
Best for:
- product cards
- article cards
- promo banners
- profile cards
FluxCard(
layout: FluxLayoutMode.column,
...
)
Row
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: ...
),
...
)
Expandable Media (isMediaExpanded)
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.)
Responsive
Switches between column and row based on the card theme breakpoint.
FluxCard(
layout: FluxLayoutMode.responsive,
fullWidth: true,
...
)
Tip
responsive works best when the card has a known width or uses fullWidth: true.
Inline
Useful for denser inline arrangements where the card behaves more like a structured content row.
Examples
Product card
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: () {},
)
Breakout promo card
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,
)
Ticket card
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.'),
)
Accessibility
semanticLabellets you describe the card for assistive technologiesNEWmergeSemantics: 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
Widgetbook and examples
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
Version 0.2.0 notes
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.
Roadmap ideas
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