Styling components
Define appearance, respond to interaction states, and reuse Remix styles
Start a style with the component's concrete styler and chain the properties you need. Keep application values and callbacks on the widget; put appearance in the styler. This is the same separation used throughout Mix.
Respond to interaction states
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
final buttonStyle = ButtonStyler()
.padding(.horizontal(16))
.padding(.vertical(10))
.color(Colors.blue)
.borderRadius(.circular(8))
.label(.color(Colors.white))
.onHovered(.color(Colors.blue.shade700))
.onPressed(.scale(0.95));Remix tracks hover and press for the component. The state-specific styles describe the appearance to apply; you do not need another hover detector to drive these variants. Use the component reference for its disabled, loading, and focus behavior.
Animate style changes
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
final animatedButton = ButtonStyler()
.color(Colors.blue)
.onHovered(.color(Colors.blue.shade700))
.onPressed(.scale(0.95))
.animate(AnimationConfig.spring(300.ms));The animation applies to style transitions, including the color and scale in this example. It does not supply application behavior such as saving data or deciding when a loading state begins.
Reduced motion
Remix reads two signals. MediaQuery.disableAnimations is set by Android and
desktop settings and can be overridden for a subtree. The platform's
accessibility reduceMotion is the only flag iOS Reduce Motion sets. Motion is
reduced when either is on. A MediaQuery override can force reduced motion on
for a subtree, but it cannot turn the platform's reduceMotion off.
The select menu, accordion panel, skeleton, and sidebar follow both signals. An active select or accordion transition settles when the preference changes; essential spinner motion keeps running. Dialogs, tooltips, disclosure, and toast get their motion from Naked UI, so they follow whichever signals the installed Naked UI release reads.
Read the same preference for your own motion with RemixReducedMotionBuilder.
Its builder runs again when either signal changes.
import 'package:flutter/widgets.dart';
import 'package:remix/remix.dart';
Widget fadingPanel({required bool open, required Widget child}) {
return RemixReducedMotionBuilder(
builder: (context, reduceMotion) => AnimatedOpacity(
opacity: open ? 1 : 0,
duration: reduceMotion
? Duration.zero
: const Duration(milliseconds: 300),
child: child,
),
);
}Style animations declared with .animate(...), including those in installed
presets, are driven by Mix, which never reads reduced motion and gives Remix no
point to intercept it. Flutter already shortens Mix's curve animations while the
platform's disableAnimations is set. Springs, iOS Reduce Motion, and a
MediaQuery override leave them running. Choose the animation yourself while
reduceMotion is true. Mix keeps the old animation driver while a style is
moving, so dropping .animate takes effect once the style settles:
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
Widget saveButton(VoidCallback save) {
final base = ButtonStyler()
.color(Colors.blue)
.onHovered(.color(Colors.blue.shade700));
return RemixReducedMotionBuilder(
builder: (context, reduceMotion) => RemixButton(
label: 'Save',
onPressed: save,
style: reduceMotion
? base
: base.animate(AnimationConfig.spring(300.ms)),
),
);
}Reuse a base style
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
final baseButton = ButtonStyler()
.padding(.horizontal(16))
.padding(.vertical(10))
.borderRadius(.circular(8));
final primaryButton = baseButton
.color(Colors.blue)
.label(.color(Colors.white));
final destructiveButton = baseButton
.color(Colors.red)
.label(.color(Colors.white));Each chain returns a new style, leaving baseButton unchanged. Both variants
retain the shared spacing and radius while defining their own colors.
Choose between a style and an installed recipe
Use a styler directly when the application only needs a reusable appearance. Use Open Code when you want a local token system, named component variants, and generated widget adapters that your team can maintain.
Follow the Styler API for method names and contextual shorthand.
Use canonical calls such as .padding(.horizontal(16)) for spacing.