flutter_tweakcn_generator 0.4.0 copy "flutter_tweakcn_generator: ^0.4.0" to clipboard
flutter_tweakcn_generator: ^0.4.0 copied to clipboard

Converts tweakcn CSS themes into Flutter ThemeData with ColorScheme, ThemeExtension, Google Fonts, and light/dark mode support.

flutter_tweakcn_generator #

A code generator that converts tweakcn CSS themes into Flutter ThemeData, ColorScheme, and ThemeExtension classes.

Features #

  • Color formats: hex, rgb(), hsl(), oklch()
  • Light / Dark mode: auto-split from :root / .dark blocks
  • ColorScheme mapping
  • ThemeExtension generation: Colors, Radius, Shadows
  • Google Fonts: auto-detects --font-sans and generates GoogleFonts.xxxTextTheme() with fontFamilyFallback support
  • Local Fonts: font_mode: local downloads .ttf files at build time and uses fontFamily directly (no runtime dependency)
  • Custom Fonts: font_mode: custom uses user-provided .ttf files for fonts not available on Google Fonts
  • BuildContext extensions: context.tweakcnColors, context.tweakcnRadius, context.tweakcnShadows
  • CLI and build_runner support

Getting Started #

1. Install #

# pubspec.yaml
dev_dependencies:
  flutter_tweakcn_generator: ^0.4.0

2. Prepare CSS #

Customize your theme at tweakcn.com, copy the CSS, and save it as tweakcn.css in your project root.

:root {
  --background: #ffffff;
  --foreground: #0a0a0a;
  --primary: #171717;
  --primary-foreground: #fafafa;
  /* ... */
  --font-sans: 'Inter', sans-serif;
  --radius: 0.625rem;
}

.dark {
  --background: #0a0a0a;
  --foreground: #fafafa;
  /* ... */
}

3. Generate #

dart run flutter_tweakcn_generator

Reads tweakcn.css and generates lib/theme/tweakcn_theme.g.dart by default.

If a Google Font is detected in --font-sans, the google_fonts package is automatically added to your pubspec.yaml.

4. Usage #

import 'theme/tweakcn_theme.g.dart';

MaterialApp(
  theme: TweakcnTheme.light,
  darkTheme: TweakcnTheme.dark,
);

Access tokens in widgets:

// Colors
final bg = context.tweakcnColors.background;
final primary = context.tweakcnColors.primary;
final sidebarBg = context.tweakcnColors.sidebar;

// Radius
final borderRadius = BorderRadius.circular(context.tweakcnRadius.lg);

// Shadows
Container(
  decoration: BoxDecoration(
    boxShadow: context.tweakcnShadows.shadowMd,
  ),
);

Configuration #

Customize settings in pubspec.yaml:

flutter_tweakcn_generator:
  input: tweakcn.css                        # CSS file path (default)
  output: lib/theme/tweakcn_theme.g.dart    # output path (default)
  class_prefix: Tweakcn                     # class name prefix (default)
  font_mode: google_fonts                   # google_fonts (default) | local | custom
  font_dir: fonts                           # local font directory (default: fonts)
  font_exclusive: false                     # auto-clean unused fonts (default: false, local mode only)
  font_exclusive_allow_empty: false         # clean up even when no --font-sans is declared (default: false)

When font_exclusive: true is set with font_mode: local, fonts in the fonts/ directory that are no longer referenced by --font-sans are automatically deleted, and their flutter > fonts declarations are removed from pubspec.yaml. Useful when switching fonts to keep the project clean.

Cleanup is skipped with a warning when no --font-sans is found in the :root block, since deleting every font file on the basis of a parsing miss is not recoverable in custom mode. Switching --font-sans to a pure system stack (ui-sans-serif, system-ui, ...) still cleans up as before — that is a declared intent, not a missing value.

Set font_exclusive_allow_empty: true to clean up anyway when the CSS declares no --font-sans at all.

Changing class_prefix renames the generated classes:

// class_prefix: My
MyTheme.light
context.myColors.primary
context.myRadius.lg
context.myShadows.shadowMd

build_runner #

Name the CSS with the *.tweakcn.css extension and generate with build_runner:

dart run build_runner build

Each <name>.tweakcn.css becomes a <name>.tweakcn.dart beside it, so lib/app.tweakcn.css generates lib/app.tweakcn.dart. The input and output settings under flutter_tweakcn_generator: in pubspec.yaml are for the CLI and have no effect here — build_runner decides both from the input's own path.

Configure builder options in build.yaml:

targets:
  $default:
    builders:
      flutter_tweakcn_generator|tweakcn:
        options:
          class_prefix: Tweakcn       # class name prefix (default)
          font_mode: google_fonts     # google_fonts (default) | local | custom

Generated Code #

Output Description
ColorScheme (light/dark) CSS colors mapped to Material ColorScheme
TweakcnColors All color tokens (ThemeExtension), plus a fromMap factory
TweakcnRadius sm, md, lg, xl (ThemeExtension)
TweakcnShadows shadow-2xs through shadow-2xl (ThemeExtension)
TweakcnTheme ThemeData.light / ThemeData.dark
TweakcnBuildContext Convenience extensions like context.tweakcnColors

Building a theme at runtime #

TweakcnTheme.light and TweakcnTheme.dark are baked in at generation time. To render a theme the user supplies — pasting tweakcn CSS and previewing it live, say — parse the CSS and hand the tokens to the generated factory:

import 'package:flutter_tweakcn_generator/flutter_tweakcn_generator.dart';

import 'theme/tweakcn_theme.g.dart';

final theme = CssParser.parse(css);

MaterialApp(
  theme: ThemeData(
    extensions: [
      TweakcnColors.fromMap(theme.light.colors),
      TweakcnRadius.fromRadius(theme.light.radius ?? theme.dark.radius),
      TweakcnShadows.fromShadowMap(theme.light.shadowLayers),
    ],
  ),
);

fromMap takes the parsed token map — CSS variable names without --, mapped to 32-bit ARGB — and covers exactly the tokens the generator writes, so it keeps covering them when tokens are added. A token the CSS does not define gets the same transparent placeholder the generated constants use, which means building from a theme's own tokens reproduces that theme's constants.

fromRadius derives the four steps the same way the generated constant does: lg is the radius itself, md two less, sm four less, xl four more, and no step goes below zero. Pass null for a theme that declares no --radius and it falls back to the same base the constant was built from. A ThemeData carries one radius, so hand it light's and fall back to dark's, exactly as the generator does.

fromShadowMap takes the levels keyed by CSS variable name, each holding its layers in paint order. A level the CSS does not define comes out empty, as it does in the constants. Shadow layers are the one token that is not a plain number, so the generated file declares a record type for them:

typedef TweakcnShadowLayer = ({
  double offsetX,
  double offsetY,
  double blurRadius,
  double spreadRadius,
  int color,
});

ThemeModeData.shadowLayers produces exactly that shape, so you never write it out yourself — but because a record is structural, you can, without importing anything.

None of these factories name a type from this package, so the generated file keeps importing Flutter and nothing else, and passing their results around costs you no dependency. Only CssParser does — parsing at runtime means moving flutter_tweakcn_generator from dev_dependencies to dependencies. If your CSS is fixed at build time you do not need any of this: use TweakcnTheme.light and TweakcnTheme.dark.

ColorScheme Mapping #

CSS Variable ColorScheme Property
--background surface
--foreground onSurface
--primary primary
--primary-foreground onPrimary
--secondary secondary
--secondary-foreground onSecondary
--destructive error
--destructive-foreground onError
--border outline
--input outlineVariant
--card surfaceContainerLowest
--muted surfaceContainerHighest
--muted-foreground onSurfaceVariant

The first eight rows are required parameters of Flutter's ColorScheme, so they are always emitted. When your CSS does not define one, a fallback is substituted and the token is named in a warning: on* colors become black or white by contrast against their base color, a missing secondary reuses primary, and anything left over falls back to Material's own baseline. The remaining rows are optional and are emitted only when defined.

Google Fonts #

When --font-sans contains a specific font name, a textTheme is generated using the google_fonts package:

/* Generates: GoogleFonts.interTextTheme() */
--font-sans: 'Inter', sans-serif;

/* Generates: GoogleFonts.notoSansKrTextTheme() */
--font-sans: 'Noto Sans KR', sans-serif;

/* No generation (system font stack) */
--font-sans: ui-sans-serif, system-ui, sans-serif;

Font Fallback

Multiple Google Fonts in --font-sans are supported as fallback fonts. The first font becomes the primary textTheme, and the rest are added to fontFamilyFallback. This is useful for CJK (Korean, Japanese, Chinese) font support:

/* Primary: Architects Daughter, Fallback: Noto Sans KR */
--font-sans: 'Architects Daughter', 'Noto Sans KR', sans-serif;

Generates:

textTheme: GoogleFonts.architectsDaughterTextTheme().apply(
  fontFamilyFallback: [GoogleFonts.notoSansKr().fontFamily!],
),

Characters not found in the primary font (e.g. Korean) automatically fall back to the next font.

Local Fonts #

Set font_mode: local to download .ttf files at generation time instead of using the google_fonts package at runtime:

flutter_tweakcn_generator:
  font_mode: local

When you run dart run flutter_tweakcn_generator:

  1. .ttf files are downloaded from Google Fonts into the fonts/ directory (customizable via font_dir)
  2. pubspec.yaml is updated with flutter > fonts declarations
  3. Generated code uses fontFamily / fontFamilyFallback instead of GoogleFonts
// font_mode: local
static ThemeData get light => ThemeData(
  fontFamily: 'Architects Daughter',
  fontFamilyFallback: ['Noto Sans KR'],
  // ...
);

This is useful when you want to avoid runtime font downloads or need to work offline.

Custom Fonts #

Set font_mode: custom to use your own .ttf files that are not available on Google Fonts:

flutter_tweakcn_generator:
  font_mode: custom
  font_dir: fonts   # directory containing your .ttf files

Place your .ttf files in the fonts/ directory with the naming convention {FontName}-{Weight}.ttf:

fonts/
  MyCustomFont-Regular.ttf
  MyCustomFont-Bold.ttf
  MyCustomFont-Light.ttf

Supported weight suffixes: Thin (100), ExtraLight (200), Light (300), Regular (400), Medium (500), SemiBold (600), Bold (700), ExtraBold (800), Black (900).

When you run dart run flutter_tweakcn_generator:

  1. .ttf files matching the --font-sans font name are scanned from the fonts/ directory
  2. pubspec.yaml is updated with flutter > fonts declarations (with auto-detected weights)
  3. Generated code uses fontFamily / fontFamilyFallback (same as local mode)

If no matching .ttf files are found, a warning is printed and font registration is skipped.

Platform Setup (Google Fonts) #

When using google_fonts, the app needs network access to download fonts at runtime. The following platform-specific configuration is required:

macOS #

Add com.apple.security.network.client to both entitlements files:

macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements:

<key>com.apple.security.network.client</key>
<true/>

Without this, macOS sandbox blocks outgoing connections and you'll get Operation not permitted errors.

Android #

Add the INTERNET permission to android/app/src/main/AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET"/>
    ...
</manifest>

The debug/AndroidManifest.xml includes this by default, but release builds require it in main.

iOS / Windows / Web #

No additional configuration needed. Network access is allowed by default.

Supported Color Formats #

--primary: #171717;                /* hex */
--primary: rgb(23, 23, 23);       /* rgb */
--primary: hsl(0 0% 9%);          /* hsl */
--primary: oklch(0.21 0 0);       /* oklch */

Guides #

  • Using with Riverpod

Development #

dart test    # the generator's own test suite

The suite checks the text the generator emits. It cannot check that the text compiles, because this package generates Flutter source without depending on Flutter. That takes a second command:

cd example && flutter pub get && cd ..
dart run tool/verify_generated_output.dart

It generates a theme for the inputs that stress the generator differently — a complete theme, a minimal one, one with no colors at all, one that defines only the .dark block, and one per way the theme class can name a font — analyzes them inside example/ where package:flutter resolves, and deletes them again. It exits non-zero and reprints the analyzer's own message when any of them fails to compile.

It also checks that the theme committed under example/ is still what the generator writes for the example's CSS. If it is not, regenerate it — the example is committed exactly as generated, so that it shows what a consumer's output actually looks like:

cd example && dart run flutter_tweakcn_generator

Run it whenever you change what the generator emits. dart test compares the emitted text against text, so it cannot catch a generated file that does not build: a ColorScheme missing a parameter a new Flutter release made required, an unbalanced textTheme: ...apply(...), or a GoogleFonts method name derived from a family that the package does not spell that way.

CI runs both commands on every push and pull request.

License #

MIT

1
likes
0
points
367
downloads

Publisher

verified publisherkihyun1998.com

Weekly Downloads

Converts tweakcn CSS themes into Flutter ThemeData with ColorScheme, ThemeExtension, Google Fonts, and light/dark mode support.

Repository (GitHub)
View/report issues

Topics

#theme #code-generation #css #design-system #shadcn

License

unknown (license)

Dependencies

build, dart_style, path, pub_semver, yaml

More

Packages that depend on flutter_tweakcn_generator