flux_card 0.2.0
flux_card: ^0.2.0 copied to clipboard
A constraint-aware, domain-agnostic, composition-first card layout engine for Flutter.
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 - [NEW] zero-boilerplate
FluxCard.simple()factory - [NEW] expandable 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 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
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
