Skip to content

Instantly share code, notes, and snippets.

@aloisdeniel
Last active January 19, 2026 17:34
Show Gist options
  • Select an option

  • Save aloisdeniel/0d7f5a4c1d1f9be775a0530a2c2b1ba1 to your computer and use it in GitHub Desktop.

Select an option

Save aloisdeniel/0d7f5a4c1d1f9be775a0530a2c2b1ba1 to your computer and use it in GitHub Desktop.
Flutter Codegen plugin for Figma / Variables and Styles
import 'package:flutter/material.dart';
// The code below has been generated with the Flutter Codegen plugin for Figma.
import 'dart:ui' as ui;
import 'package:flutter/widgets.dart' as fl;
import 'package:google_fonts/google_fonts.dart' as gf;
typedef VariableMode = ({ColorsMode colors, AccentMode accent, FontsMode fonts});
extension VariableModeExtension on VariableMode {
VariableMode copyWith({
ColorsMode? colors,
AccentMode? accent,
FontsMode? fonts,
}) {
return (
colors: colors ?? this.colors,
accent: accent ?? this.accent,
fonts: fonts ?? this.fonts,
);
}
}
class VariableCollections {
const VariableCollections._({required this.colors, required this.accent, required this.fonts, required this.spacing, required this.radii, required this.styles});
factory VariableCollections.fromModes(VariableMode mode) {
final colors = ColorsData.fromMode(mode.colors);
final accent = AccentData.fromMode(mode.accent);
final fonts = FontsData.fromMode(mode.fonts);
final spacing = SpacingData();
final radii = RadiiData();
final styles = StylesData();
// Resolving aliases
colors.alias = (accent: accent);
styles.alias = (fonts: fonts);
return VariableCollections._(colors: colors, accent: accent, fonts: fonts, spacing: spacing, radii: radii, styles: styles);
}
final ColorsData colors;
final AccentData accent;
final FontsData fonts;
final SpacingData spacing;
final RadiiData radii;
final StylesData styles;
}
class Variables extends fl.InheritedWidget {
Variables({super.key, required super.child, required this.mode})
: data = VariableCollections.fromModes(mode);
final VariableMode mode;
final VariableCollections data;
factory Variables.override(
fl.BuildContext context, {
fl.Key? key,
required fl.Widget child,
ColorsMode? colors,
AccentMode? accent,
FontsMode? fonts,
}) {
final mode = Variables.modeOf(context);
return Variables(
key: key,
mode: mode!.copyWith(
colors: colors,
accent: accent,
fonts: fonts,
),
child: child,
);
}
@override
bool updateShouldNotify(covariant Variables oldWidget) {
return mode != oldWidget.mode;
}
static VariableCollections? maybeOf(fl.BuildContext context) {
final instance = context.dependOnInheritedWidgetOfExactType<Variables>();
return instance?.data;
}
static VariableCollections of(fl.BuildContext context) {
final data = maybeOf(context);
assert(data != null, 'No Variables found in context');
return data!;
}
static VariableMode? modeOf(fl.BuildContext context) {
final instance = context.dependOnInheritedWidgetOfExactType<Variables>();
return instance?.mode;
}
}
enum ColorsMode {
light,
dark,
}
sealed class ColorsData {
ColorsData();
factory ColorsData.fromMode(ColorsMode mode) {
switch (mode) {
case ColorsMode.light:
return _Light();
case ColorsMode.dark:
return _Dark();
}
}
late ({AccentData accent}) alias;
fl.Color get backgroundMain;
fl.Color get backgroundSecondary;
fl.Color get foregroundMain;
fl.Color get backgroundCover;
fl.Color get foregroundSecondary;
fl.Color get backgroundPrimary;
fl.Color get foregroundPrimary;
fl.Color get foregroundOnPrimary;
}
class _Light extends ColorsData {
_Light();
@override
final backgroundMain = const fl.Color.from(red: 1, green: 1, blue: 1, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final backgroundSecondary = const fl.Color.from(red: 0.9657426476478577, green: 0.9651665091514587, blue: 0.9709284901618958, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final foregroundMain = const fl.Color.from(red: 0.0941176488995552, green: 0.0941176488995552, blue: 0.10196078568696976, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final backgroundCover = const fl.Color.from(red: 0.0941176488995552, green: 0.0941176488995552, blue: 0.10196078568696976, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final foregroundSecondary = const fl.Color.from(red: 0.2168998122215271, green: 0.2168998122215271, blue: 0.286865234375, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
get backgroundPrimary => alias.accent.medium;
@override
get foregroundPrimary => alias.accent.dark;
@override
get foregroundOnPrimary => alias.accent.light;
}
class _Dark extends ColorsData {
_Dark();
@override
final backgroundMain = const fl.Color.from(red: 0.1282019019126892, green: 0.1282019019126892, blue: 0.14614632725715637, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final backgroundSecondary = const fl.Color.from(red: 0.20936547219753265, green: 0.20936547219753265, blue: 0.23069411516189575, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final foregroundMain = const fl.Color.from(red: 1, green: 1, blue: 1, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final backgroundCover = const fl.Color.from(red: 0.0941176488995552, green: 0.0941176488995552, blue: 0.10196078568696976, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final foregroundSecondary = const fl.Color.from(red: 0.915771484375, green: 0.8869644403457642, blue: 0.8869644403457642, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
get backgroundPrimary => alias.accent.medium;
@override
get foregroundPrimary => alias.accent.dark;
@override
get foregroundOnPrimary => alias.accent.light;
}
enum AccentMode {
blue,
green,
}
sealed class AccentData {
AccentData();
factory AccentData.fromMode(AccentMode mode) {
switch (mode) {
case AccentMode.blue:
return _Blue();
case AccentMode.green:
return _Green();
}
}
fl.Color get light;
fl.Color get medium;
fl.Color get dark;
}
class _Blue extends AccentData {
_Blue();
@override
final light = const fl.Color.from(red: 0.9249361753463745, green: 0.9374467730522156, blue: 1, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final medium = const fl.Color.from(red: 0.26528695225715637, green: 0.3877391517162323, blue: 1, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final dark = const fl.Color.from(red: 0.16449296474456787, green: 0.2027168571949005, blue: 0.3938363790512085, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
}
class _Green extends AccentData {
_Green();
@override
final light = const fl.Color.from(red: 0.9699666500091553, green: 1, blue: 0.836181640625, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final medium = const fl.Color.from(red: 0.1932700127363205, green: 0.608229398727417, blue: 0.4353296458721161, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
@override
final dark = const fl.Color.from(red: 0.19369162619113922, green: 0.283203125, blue: 0.23397180438041687, alpha: 1,colorSpace: ui.ColorSpace.extendedSRGB);
}
enum FontsMode {
regular,
monospace,
}
sealed class FontsData {
FontsData();
factory FontsData.fromMode(FontsMode mode) {
switch (mode) {
case FontsMode.regular:
return _Regular();
case FontsMode.monospace:
return _Monospace();
}
}
String get familyMain;
double get fontSizeM;
double get fontSizeL;
double get fontSizeXl;
double get weightNormal;
double get weightBold;
}
class _Regular extends FontsData {
_Regular();
@override
final familyMain = 'Inter';
@override
final fontSizeM = 11.0;
@override
final fontSizeL = 12.0;
@override
final fontSizeXl = 24.0;
@override
final weightNormal = 400.0;
@override
final weightBold = 600.0;
}
class _Monospace extends FontsData {
_Monospace();
@override
final familyMain = 'Fira Code';
@override
final fontSizeM = 11.0;
@override
final fontSizeL = 11.0;
@override
final fontSizeXl = 22.0;
@override
final weightNormal = 400.0;
@override
final weightBold = 700.0;
}
class SpacingData {
SpacingData();
final xxs = 1.0;
final xs = 2.0;
final s = 4.0;
final m = 8.0;
final l = 16.0;
final xl = 24.0;
final xxl = 32.0;
}
class RadiiData {
RadiiData();
final xxs = 1.0;
final xs = 2.0;
final s = 4.0;
final m = 8.0;
final l = 12.0;
final xl = 24.0;
final xxl = 48.0;
}
class StylesData {
StylesData();
late ({FontsData fonts}) alias;
late final title = gf.GoogleFonts.getFont(alias.fonts.familyMain, textStyle: fl.TextStyle(fontSize: alias.fonts.fontSizeXl, fontWeight: fl.FontWeight.values.firstWhere(
(e) => alias.fonts.weightBold <= e.value.toInt(),
orElse: () => fl.FontWeight.w400,
)));
late final body = gf.GoogleFonts.getFont(alias.fonts.familyMain, textStyle: fl.TextStyle(fontSize: alias.fonts.fontSizeM, fontWeight: fl.FontWeight.values.firstWhere(
(e) => alias.fonts.weightNormal <= e.value.toInt(),
orElse: () => fl.FontWeight.w400,
)));
late final callout = gf.GoogleFonts.getFont(alias.fonts.familyMain, textStyle: fl.TextStyle(fontSize: alias.fonts.fontSizeL, fontWeight: fl.FontWeight.values.firstWhere(
(e) => alias.fonts.weightBold <= e.value.toInt(),
orElse: () => fl.FontWeight.w400,
)));
}
// The code below is a usage example corresponding to the original
// component in Figma.
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
// We provide root default modes.
builder: (context, child) => Variables(
mode: (
colors: switch (MediaQuery.platformBrightnessOf(context)) {
Brightness.light => ColorsMode.light,
Brightness.dark => ColorsMode.dark,
},
accent: AccentMode.blue,
fonts: FontsMode.regular,
),
child: DefaultTextStyle(
style: TextStyle(decoration: TextDecoration.none),
child: child!,
),
),
home: const Home(),
);
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
VariableMode? _modeOverride;
@override
Widget build(BuildContext context) {
final mode = _modeOverride ?? Variables.modeOf(context)!;
return Variables.override(
context,
colors: _modeOverride?.colors,
fonts: _modeOverride?.fonts,
accent: _modeOverride?.accent,
child: Column(
children: [
ModesBar(
mode: mode,
onModeChange: (newMode) {
setState(() {
_modeOverride = newMode;
});
},
),
Expanded(child: const Example()),
],
),
);
}
}
class ModesBar extends StatelessWidget {
const ModesBar({super.key, required this.mode, required this.onModeChange});
final VariableMode mode;
final ValueChanged<VariableMode> onModeChange;
@override
Widget build(BuildContext context) {
final mode = Variables.modeOf(context)!;
final vars = Variables.of(context);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Button(
label: '${mode.colors.name}',
onPressed: () {
final newColors = switch (mode.colors) {
ColorsMode.light => ColorsMode.dark,
ColorsMode.dark => ColorsMode.light,
};
onModeChange(mode.copyWith(colors: newColors));
},
),
const SizedBox(width: 16.0),
Button(
label: '${mode.accent.name}',
onPressed: () {
final newAccent = AccentMode
.values[(mode.accent.index + 1) % AccentMode.values.length];
onModeChange(mode.copyWith(accent: newAccent));
},
),
const SizedBox(width: 16.0),
Button(
label: '${mode.fonts.name}',
onPressed: () {
final newFonts = FontsMode
.values[(mode.fonts.index + 1) % FontsMode.values.length];
onModeChange(mode.copyWith(fonts: newFonts));
},
),
],
);
}
}
class Example extends StatelessWidget {
const Example({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final vars = Variables.of(context);
return Container(
color: vars.colors.backgroundCover,
child: Center(
child: Container(
padding: EdgeInsets.all(vars.spacing.xl),
decoration: BoxDecoration(
color: vars.colors.backgroundMain,
borderRadius: BorderRadius.circular(vars.radii.l),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Hello, World!',
style: vars.styles.title.copyWith(
color: vars.colors.foregroundMain,
),
),
SizedBox(height: vars.spacing.m),
Text(
'This is an example of using generated design tokens in Flutter.',
style: vars.styles.body.copyWith(
color: vars.colors.foregroundMain,
),
),
SizedBox(height: vars.spacing.l),
IntrinsicWidth(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Button(
label: 'Cancel',
style: ButtonStyleVariant.secondary,
onPressed: () {},
),
Button(label: 'Continue', onPressed: () {}),
],
),
),
],
),
),
),
);
}
}
enum ButtonStyleVariant { primary, secondary }
class Button extends StatelessWidget {
const Button({
super.key,
required this.label,
required this.onPressed,
this.style = ButtonStyleVariant.primary,
});
final String label;
final ButtonStyleVariant style;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
final vars = Variables.of(context);
final background = switch (style) {
ButtonStyleVariant.primary => vars.colors.backgroundPrimary,
ButtonStyleVariant.secondary => vars.colors.backgroundSecondary,
};
final foreground = switch (style) {
ButtonStyleVariant.primary => vars.colors.foregroundOnPrimary,
ButtonStyleVariant.secondary => vars.colors.foregroundSecondary,
};
return GestureDetector(
onTap: onPressed,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
decoration: BoxDecoration(
color: background,
borderRadius: BorderRadius.circular(100.0),
),
child: Text(
label,
style: vars.styles.callout.copyWith(color: foreground),
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment