Core concepts
Fabrik packages are independent, but they are built on the same few ideas. Learning them once makes every package predictable.
Semantic names over raw values
Section titled “Semantic names over raw values”The recurring theme: name things by what they mean, not by what they look
like. context.colors.error says why the color was chosen; Colors.red only
says what it is. When the brand changes, one is a one-line edit and the other
is a search across the codebase.
// Escapes the design system the moment it is writtenText('Payment failed', style: TextStyle(color: Colors.red))
// Stays inside itText('Payment failed', style: TextStyle(color: context.colors.error))The same idea drives SpacingTokens.md over 16, and
context.layout.isMobile over MediaQuery.of(context).size.width < 600.
Context extensions
Section titled “Context extensions”Anything resolved from the widget tree is read through an extension on
BuildContext. There is nothing to pass down and nothing to inject.
context.colors.primary // fabrik_themecontext.typography.bodyLarge // fabrik_themecontext.layout.isMobile // fabrik_layoutThese are thin wrappers over Theme.of and InheritedWidget lookups. They
introduce no extra rebuilds — and they throw a StateError with a fix-it
message if the relevant ancestor is missing, rather than failing silently or
returning a wrong default.
StateError: AppColors not found in ThemeData.extensions.Did you forget to use FabrikTheme.create?Configuration objects
Section titled “Configuration objects”Anything with more than a couple of options takes a single config object
rather than a long parameter list. Configs are const-constructible, so they
can live as static values and be reused.
const errorSnackbar = FabrikSnackbarConfig( title: 'Upload failed', position: FabrikSnackbarPosition.top, style: FabrikSnackbarStyle.grounded,);
FabrikSnackbar.custom(context, config: errorSnackbar);Explicit failure
Section titled “Explicit failure”Fabrik prefers failures you can see in a type signature over failures that surface at runtime.
// The signature says nothing about what can go wrongFuture<User> getUser(String id);
// The signature is the documentationFuture<Either<Failure, User>> getUser(String id);Where a mistake cannot be encoded in a type, it becomes a loud error with a message that names the fix — an unknown form field lists the valid keys, a missing theme extension names the factory to call.
Progressive disclosure
Section titled “Progressive disclosure”Every package has a default that works, and every default can be replaced. You should be able to get something on screen before learning the whole API.
// Day oneFabrikSnackbar.success(context, title: 'Saved');
// Later, when the design system says otherwiseFabrikSnackbar.custom( context, config: FabrikSnackbarConfig( title: 'Saved', backgroundGradient: brandGradient, style: FabrikSnackbarStyle.grounded, dismissDirection: FabrikSnackbarDismissDirection.horizontal, ),);The same pattern holds elsewhere: AppColors.defaults() gets you a working
theme, FabrikBreakpoints() gets you sensible breakpoints, and both are one
argument away from being fully yours.
Value semantics
Section titled “Value semantics”Every configuration and data class implements == and hashCode. Two
instances with the same values are equal, which means they can be compared in
tests, used as map keys, and relied on by Flutter to skip unnecessary
rebuilds.
AppColors.defaults() == AppColors.defaults(); // trueconst FabrikBreakpoints() == const FabrikBreakpoints(); // trueTheme classes go further and implement lerp, so switching between light and
dark animates every semantic color rather than snapping.