Accordion
An expandable/collapsible component for showing and hiding content panels with support for multiple expansion modes
An accordion component that manages expansion state of content panels with min/max constraints.
When to use this
- FAQ sections: Display frequently asked questions with expandable answers
- Content organization: Organize large amounts of content in a compact, scannable format
- Settings panels: Group related settings that can be shown/hidden
- Navigation menus: Create collapsible menu structures with nested content
Anatomy
An accordion item is one panel with two parts inside it:
container— the outer panel. It owns the shared frame: fill, border, radius, and the clip that crops both parts into that rounded shape. UsecontainerEffectsfor layered fills, strokes, and backdrop blur painted with it.trigger— the always-visible header row (leading icon, title, trailing icon). It owns only its own internal layout, such as padding and spacing.content— the expanded body. It owns its own padding and any interior divider, drawn as a foreground border so it does not inset the body by a pixel.
Because the container supplies the rounding, neither the trigger nor the content rounds its own corners, so an expanded trigger meets its content with no notch.
Widget-state variants (onHovered, onPressed, onFocused, onFocusVisible,
onDisabled) describe interaction with the trigger, but may style any part
of the item, including the container. The expanded content is not a separate
hover or press target — moving the pointer into it clears the hovered state.
The top-level Box shorthand on AccordionStyler (.color(), .padding(),
.borderRadius(), .decoration(), ...) forwards to trigger, not to
container. That forwarding predates the panel and is kept for backward
compatibility; reach for .container(...) explicitly when you mean the
outer frame.
Basic implementation
Basic implementation
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
class AccordionExample extends StatefulWidget {
const AccordionExample({super.key});
@override
State<AccordionExample> createState() => _AccordionExampleState();
}
class _AccordionExampleState extends State<AccordionExample> {
final controller = RemixAccordionController<String>(min: 0, max: 1);
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return RemixAccordionGroup(
controller: controller,
child: ColumnBox(
style: FlexBoxStyler().spacing(16),
children: [
RemixAccordion(
value: 'accordion1',
title: 'How do I update my account information?',
leadingIcon: Icons.help_outline,
style: itemStyle,
child: const Text(
'Insert the accordion description here. It would look better as two lines of text.',
),
),
RemixAccordion(
value: 'accordion2',
title: 'What payment methods are accepted?',
leadingIcon: Icons.help_outline,
style: itemStyle,
child: const Text(
'Major credit and debit cards like Visa, MasterCard, and American Express, as well as digital payment options like PayPal and Apple Pay.'),
),
RemixAccordion(
value: 'accordion3',
title: 'How can I track my order?',
leadingIcon: Icons.help_outline,
style: itemStyle,
child: const Text(
'You can track your order status in the "My Orders" section of your account.'),
),
],
),
);
}
AccordionStyler get itemStyle {
return AccordionStyler()
// The container owns the shared frame — one fill, one border, one
// radius, and the clip that crops the trigger and the content into it.
.container(
BoxStyler()
.color(Colors.white)
.border(
BoxBorderMix.all(
BorderSideMix().color(Colors.grey.shade300).width(1),
),
)
.borderRadius(.circular(8))
.clipBehavior(Clip.antiAlias),
)
// Trigger and content own only their own internal layout.
.trigger(
FlexBoxStyler()
.direction(Axis.horizontal)
.mainAxisAlignment(MainAxisAlignment.spaceBetween)
.spacing(12)
.padding(.horizontal(16))
.padding(.vertical(14)),
)
.content(BoxStyler().padding(.horizontal(16)).padding(.bottom(14)))
// A widget-state variant tracks the trigger, and can style any part.
.onHovered(
AccordionStyler().container(BoxStyler().color(Colors.grey.shade100)),
)
.leadingIcon(IconStyler().color(Colors.grey.shade700).size(20))
.title(
TextStyler()
.color(Colors.grey.shade900)
.fontWeight(FontWeight.w500)
.fontSize(14),
)
.trailingIcon(IconStyler().color(Colors.grey.shade700).size(20));
}
}Fortal widgets
The application-owned Fortal preset provides a themed widget for this component:
Fortal widget
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
import 'ui/ui.dart';
class FortalAccordionExample extends StatefulWidget {
const FortalAccordionExample({super.key});
@override
State<FortalAccordionExample> createState() => _FortalAccordionExampleState();
}
class _FortalAccordionExampleState extends State<FortalAccordionExample> {
final controller = RemixAccordionController<String>();
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return RemixAccordionGroup(
controller: controller,
// Each item is its own bordered panel, so adjacent items need spacing
// between them rather than a shared divider.
child: Column(
spacing: 8,
children: [
FortalAccordion.surface(
value: 'item1',
title: 'First Item',
size: FortalAccordionSize.size2,
child: Text('First content'),
),
FortalAccordion.soft(
value: 'item2',
title: 'Second Item',
size: FortalAccordionSize.size3,
child: Text('Second content'),
),
],
),
);
}
}See the fortalAccordionStyle source code for all available options.
Constructor
Constructor
import 'package:flutter/material.dart';
import 'package:naked_ui/naked_ui.dart';
import 'package:remix/remix.dart';
// Accordion Group
RemixAccordionGroup<T> remixAccordionGroupConstructor<T>({
Key? key,
required Widget child,
required RemixAccordionController<T> controller,
List<T> initialExpandedValues = const [],
}) => throw UnimplementedError();
// Accordion Item
RemixAccordion<T> remixAccordionConstructor<T>({
Key? key,
required T value,
required Widget child,
String? title,
IconData? leadingIcon,
IconData? trailingIcon,
NakedAccordionTriggerBuilder<T>? builder,
bool enabled = true,
MouseCursor mouseCursor = SystemMouseCursors.click,
bool enableFeedback = true,
bool autofocus = false,
FocusNode? focusNode,
ValueChanged<bool>? onFocusChange,
ValueChanged<bool>? onHoverChange,
ValueChanged<bool>? onPressChange,
String? semanticLabel,
Widget Function(Widget, Animation<double>) transitionBuilder =
RemixAccordion.defaultAccordionTransitionBuilder,
AccordionStyler style = const AccordionStyler.create(),
AccordionSpec? styleSpec,
}) => throw UnimplementedError();Properties
Widget Properties
key → Key?
Optional. Controls how one widget replaces another widget in the tree.
value → T
Required. Unique identifier tracked by the controller.
child → Widget
Required. Content rendered while expanded.
title → String?
Optional. Title text for the trigger.
leadingIcon → IconData?
Optional. Optional leading icon for the trigger.
trailingIcon → IconData?
Optional. Optional trailing icon for the trigger.
builder → NakedAccordionTriggerBuilder<T>?
Optional. Custom builder for the trigger.
enabled → bool
Optional. Whether the accordion item is interactive.
mouseCursor → MouseCursor
Optional. Mouse cursor to use when interactive.
enableFeedback → bool
Optional. Whether to provide platform feedback on interactions.
autofocus → bool
Optional. Whether the header should autofocus.
focusNode → FocusNode?
Optional. Focus node associated with the header.
onFocusChange → ValueChanged<bool>?
Optional. Called when the header's focus state changes.
onHoverChange → ValueChanged<bool>?
Optional. Called when the header's hover state changes.
onPressChange → ValueChanged<bool>?
Optional. Called when the header's pressed state changes.
semanticLabel → String?
Optional. Semantic label announced for the header.
style → AccordionStyler
Optional. The style configuration for the accordion item.
styleSpec → AccordionSpec?
Optional. A raw resolved style spec that bypasses fluent style resolution.
transitionBuilder → Widget Function(Widget, Animation<double>)
Optional. The transition builder for the accordion item.
Style Methods
container(BoxStyler value)
Configures the outer panel: fill, border, radius, and clipping shared by the
trigger and the content. Not reachable through the top-level Box shorthand,
which forwards to trigger.
containerEffects(RemixBoxEffectsMix value)
Configures layered fills, strokes, and backdrop blur painted with container.
trigger(FlexBoxStyler value)
Configures the trigger row style.
leadingIcon(IconStyler value)
Configures the leading icon style.
title(TextStyler value)
Configures the title text style.
trailingIcon(IconStyler value)
Configures the trailing icon style.
content(BoxStyler value)
Configures the expanded content container style.
The methods below are the top-level Box shorthand. Each forwards to trigger,
not to container.
backgroundColor(Color value) / color(Color value)
Sets the trigger background color.
padding(EdgeInsetsGeometryMix value)
Sets trigger padding.
margin(EdgeInsetsGeometryMix value)
Sets trigger margin.
spacing(double value)
Sets spacing between trigger children.
alignment(Alignment value)
Sets trigger alignment.
size(double width, double height)
Sets fixed trigger width and height constraints.
constraints(BoxConstraintsMix value)
Sets trigger constraints.
decoration(DecorationMix value)
Sets trigger decoration.
borderRadius(BorderRadiusGeometryMix radius)
Sets trigger border radius.
foregroundDecoration(DecorationMix value)
Sets trigger foreground decoration.
transform(Matrix4 value, {AlignmentGeometry alignment = Alignment.center})
Applies a matrix transform to the trigger.
flex(FlexStyler value)
Configures trigger flex behavior.
titleColor(Color value)
Sets title color.
titleFontSize(double value)
Sets title font size.
titleFontWeight(FontWeight value)
Sets title font weight.
titleStyle(TextStyleMix value)
Sets title style using a TextStyleMix.
leadingIconColor(Color value)
Sets leading icon color.
leadingIconSize(double value)
Sets leading icon size.
trailingIconColor(Color value)
Sets trailing icon color.
trailingIconSize(double value)
Sets trailing icon size.
contentColor(Color value)
Sets expanded content background color.
contentPadding(EdgeInsetsGeometryMix value)
Sets expanded content padding.
contentDecoration(DecorationMix value)
Sets expanded content decoration.
onExpanded<T>(AccordionStyler value)
Applies a style while the item is expanded.
onCollapsed<T>(AccordionStyler value)
Applies a style while the item is collapsed.
onCanCollapse(AccordionStyler value)
Applies a style while the item can be collapsed.
onCanExpand<T>(AccordionStyler value)
Applies a style while the item can be expanded.
onHovered / onPressed / onFocused / onFocusVisible / onDisabled
Applies a style while the trigger is in that interaction state. The state is
resolved once for the whole item, so these reach container and
containerEffects as well as the trigger, title, icons, and content.
animate(AnimationConfig value)
Configures implicit animation for style transitions.
variants(List<VariantStyle<AccordionSpec>> value)
Sets custom style variants.
wrap(WidgetModifierConfig value)
Applies widget modifiers such as clipping, opacity, or scaling.