mix_lint
Lint rules and quick fixes for Mix, the Flutter styling framework. mix_lint is an analyzer plugin: it reports problems in your IDE and in dart analyze / flutter analyze.
Requires Dart 3.12 or later (Flutter 3.44 or later).
Setup
Add the plugin to the top-level plugins section of your analysis_options.yaml. You do not need to add mix_lint to pubspec.yaml; the analysis server downloads it.
plugins:
mix_lint: ^2.0.1
Then restart the analysis server (or your IDE). Restart it again after any change to the plugins section.
Rules
Warnings catch bugs and are on by default. Lints enforce Mix style and are off until you enable them.
| Rule | Kind | Fix | Checks that |
|---|---|---|---|
missing_create_constructor |
Warning | @Mixable and @MixableStyler classes have a .create constructor |
|
token_reference_outside_mix |
Warning | token references are only passed to Mix APIs | |
base_style_after_variant |
Lint | base style calls come before variants | |
inline_token_definition |
Lint | tokens are not created inside Stylers or MixScope |
|
long_styler_chain |
Lint | Styler chains stay short | |
unnecessary_styler_constructor |
Lint | Yes | nested Stylers use dot shorthands, such as .color(...) |
unnecessary_type_name |
Lint | Yes | Styler arguments use dot shorthands |
variants_without_base_style |
Lint | Stylers set a base style before variants |
Quick fixes run in the IDE only; dart fix does not apply plugin fixes. A fix can leave an import unused, which the analyzer then reports as unused_import.
Enable lints and change severity
List rules under diagnostics. Use true to enable a lint with its default severity, error, warning, or info to set the severity, and false to turn off a rule (including a warning).
plugins:
mix_lint:
version: ^2.0.1
diagnostics:
base_style_after_variant: true
inline_token_definition: true
long_styler_chain: true
unnecessary_styler_constructor: true
unnecessary_type_name: true
variants_without_base_style: true
token_reference_outside_mix: error
Suppress a diagnostic
Prefix the rule name with the plugin name:
// ignore: mix_lint/base_style_after_variant
final style = BoxStyler().onHovered(.color(Colors.blue)).padding(.all(16));
Use // ignore_for_file: mix_lint/<rule> for a whole file.
Plugins skip files excluded in analysis_options.yaml. To keep lints out of generated code:
analyzer:
exclude:
- "**/*.g.dart"
Configure rules
Rules that take options read them from a top-level mix_lint: section of the same analysis_options.yaml. The analyzer passes plugins only diagnostics settings, so options go in this separate section.
plugins:
mix_lint:
version: ^2.0.1
diagnostics:
long_styler_chain: true
mix_lint:
long_styler_chain:
max_calls: 20
long_styler_chain takes max_calls, the most calls allowed in one chain (default 15).
Each library uses the nearest analysis_options.yaml above its file. mix_lint follows include: entries, by relative path or package: URI, and values in the including file win. In a pub workspace, put plugins: and mix_lint: in the root options file and have each member include it.
Rule reference
missing_create_constructor
Warning. A class annotated with @Mixable or @MixableStyler has no .create constructor. The generated merge() method calls .create, so the generated code does not compile without it. The rule stays silent when the annotation turns off merge() generation (for example methods: GeneratedMixMethods.skipMerge).
Don't:
@Mixable()
final class ShadowMix extends Mix<Shadow> with Diagnosticable, _$ShadowMixMixin {
final Prop<Color>? $color;
ShadowMix({Color? color}) : $color = Prop.maybe(color);
}
Do:
@Mixable()
final class ShadowMix extends Mix<Shadow> with Diagnosticable, _$ShadowMixMixin {
final Prop<Color>? $color;
ShadowMix({Color? color}) : this.create(color: Prop.maybe(color));
const ShadowMix.create({Prop<Color>? color}) : $color = color;
}
For new Stylers, prefer @MixableSpec(target: Widget.new); @MixableStyler is deprecated.
token_reference_outside_mix
Warning. Calling a token ($primary(), $primary.call(), or $body.mix()) creates a token reference. A reference only resolves inside Mix, against the surrounding MixScope. Passed to a Flutter widget or any other API outside Mix, it never resolves.
The rule reports only APIs from other packages, such as Flutter widgets and dart:core. It accepts any API that Mix declares and any Mix value type, including types generated in your package. It also stays silent for:
- functions and widgets in your own package, because they may forward the value into Mix;
- Flutter values passed to a Mix
.value()conversion, such asTextStyleMix.value(TextStyle(color: $primary())), because Mix converts them field by field and keeps a reference that reaches it unchanged. A nested value counts only when Mix has a value type for it, such asShadowMixforShadow; Mix copies other values, such asFontVariation, whole.
To read a concrete value outside Mix, use token.resolve(context).
Don't:
final $primary = ColorToken('primary');
Container(color: $primary());
Do:
final $primary = ColorToken('primary');
Box(style: BoxStyler().color($primary()));
Container(color: $primary.resolve(context));
The check is syntactic: a reference stored in a variable first (final c = $primary(); Container(color: c);) is not detected.
base_style_after_variant
A base style call, such as padding(), comes after a variant, such as onHovered(). Keep base style first and variants last so the default appearance reads in one place. animate(), keyframeAnimation(), phaseAnimation(), wrap(), modifier(), merge(), and applyVariants() may follow variants.
Don't:
final style = BoxStyler()
.color(Colors.red)
.onHovered(.color(Colors.blue))
.padding(.all(16));
Do:
final style = BoxStyler()
.color(Colors.red)
.padding(.all(16))
.onHovered(.color(Colors.blue))
.animate(.easeInOut(200.ms));
inline_token_definition
A design token is created inside a Styler chain or in the token maps of a MixScope, such as colors:. Tokens are meant to be shared: define each one once and reference it. The widget tree under child: is checked like any other code, so a token there is reported only inside a Styler chain.
Don't:
final style = BoxStyler().color(ColorToken('primary')());
MixScope(
colors: {ColorToken('primary'): Colors.blue},
child: child,
);
Do:
final $primary = ColorToken('primary');
final style = BoxStyler().color($primary());
MixScope(
colors: {$primary: Colors.blue},
child: child,
);
long_styler_chain
A Styler chain has more calls than the limit (15 by default). Split large styles into smaller Stylers and combine them with merge(). To change the limit, set max_calls (see Configure rules).
Do:
final layout = BoxStyler().padding(.all(8)).margin(.all(4)).alignment(.center);
final surface = BoxStyler().color(Colors.blue).borderRadius(.circular(8));
final card = layout.merge(surface).onHovered(.color(Colors.red));
unnecessary_styler_constructor
An empty Styler constructor starts a chain where a dot shorthand for the factory would do. The quick fix can also fix the whole file.
The rule reports:
BoxStyler().color(...)passed to another Styler method, such as a variant, where the parameter type isBoxStyler;.new().color(...)anywhere.
Both become .color(...). Every generated Styler setter has a matching factory, so the result is the same.
Don't:
final style = BoxStyler()
.color(Colors.white)
.onHovered(BoxStyler().color(Colors.blue));
final BoxStyler card = .new().padding(.all(16));
Do:
final style = BoxStyler()
.color(Colors.white)
.onHovered(.color(Colors.blue));
final BoxStyler card = .padding(.all(16));
The rule doesn't report a constructor where a dot shorthand has no Styler type to resolve against: a top-level declaration such as final style = BoxStyler()..., or a widget argument such as Box(style: BoxStyler()...), whose parameter type is Style<BoxSpec>. It also stays silent when the first call has no factory, or when the factory doesn't accept the same arguments.
unnecessary_type_name
An argument in a Styler expression names a type that dot shorthand can infer from the parameter type. A Styler expression is an argument of a Styler call, or of a Mix call nested inside one. The quick fix can also fix the whole file.
Don't:
BoxStyler().padding(EdgeInsetsGeometryMix.all(16));
TextStyler().fontWeight(FontWeight.w600);
BoxStyler().onNot(.widgetState(WidgetState.hovered), .color(Colors.grey));
Do:
BoxStyler().padding(.all(16));
TextStyler().fontWeight(.w600);
BoxStyler().onNot(.widgetState(.hovered), .color(Colors.grey));
The rule only reports a static member or named constructor of the parameter's exact type, because that is where dot shorthand looks it up. It does not report:
- members of another type, such as
Colors.bluefor aColorparameter; - unnamed constructors, such as
BoxShadowMix(...); - Mix code outside Styler expressions, such as
final v = ContextVariant.widgetState(WidgetState.hovered);; - code with a language version below 3.10.
variants_without_base_style
A Styler chain adds variants but sets no base style, so the style has no default appearance. A chain that uses onBuilder() is allowed, because it builds the whole style from context.
Don't:
final style = BoxStyler()
.onHovered(.color(Colors.blue))
.onPressed(.color(Colors.green));
Do:
final style = BoxStyler()
.color(Colors.grey)
.onHovered(.color(Colors.blue))
.onPressed(.color(Colors.green));
Renamed rules
mix_lint 2.0.0 used different rule names. See the migration table to update analysis_options.yaml and ignore comments.