Packaging Widgets as a Shared Library
Packaging Widgets as a Shared Library
As your Flutter projects grow, you will inevitably build reusable widgets that deserve to live in their own standalone package rather than being duplicated across multiple applications. A Dart package lets you extract those widgets into a versioned, independently testable unit with a clean public API. Any Flutter app can then declare it as a dependency and import it just like any pub.dev package.
Creating the Package Scaffold
Use the Flutter CLI to generate the boilerplate. The --template=package flag produces a pure-Dart package; --template=plugin would add native channels instead.
Generating a new package
# Create a package called "my_ui_kit" in a sibling directory
flutter create --template=package my_ui_kit
# Resulting structure:
# my_ui_kit/
# lib/
# my_ui_kit.dart <-- main barrel file
# test/
# pubspec.yaml
# README.md
# CHANGELOG.md
# LICENSE
Configuring pubspec.yaml
The pubspec.yaml file declares the package identity, SDK constraints, and dependencies. Keep the version following Semantic Versioning (MAJOR.MINOR.PATCH). Set publish_to: none if the package is private and not meant for pub.dev.
Sample pubspec.yaml for a widget library
name: my_ui_kit
description: A shared Flutter widget library for ESB apps.
version: 1.0.0
publish_to: none # Remove this line to publish to pub.dev
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: '>=3.10.0'
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true
Structuring the Library Source Files
Place every widget in its own file under lib/src/. The src/ directory is a convention that signals private implementation details. Files inside src/ are not directly importable by consumers — they must go through the barrel file.
Recommended library layout
lib/
my_ui_kit.dart <-- public barrel (the ONLY file consumers import)
src/
widgets/
primary_button.dart
avatar_badge.dart
status_chip.dart
theme/
app_colors.dart
app_text_styles.dart
utils/
responsive_helper.dart
Writing a Clean Barrel File
The barrel file (lib/my_ui_kit.dart) is the single entry point for consumers. It re-exports every public symbol using export directives. Prefer named show or hide clauses to control exactly which identifiers are visible.
lib/my_ui_kit.dart — the barrel file
/// My UI Kit — shared Flutter widget library.
library my_ui_kit;
export 'src/widgets/primary_button.dart';
export 'src/widgets/avatar_badge.dart';
export 'src/widgets/status_chip.dart' show StatusChip, StatusType;
export 'src/theme/app_colors.dart';
export 'src/theme/app_text_styles.dart';
src/utils/ helpers unless consumers genuinely need them. Keeping internal utilities unexported lets you refactor freely without breaking the public API.Consuming the Package in a Flutter App
There are three ways to depend on a local package: a path dependency (for co-located monorepos), a git dependency (points at a specific commit or branch), or a hosted dependency (published to pub.dev or a private registry). Path dependencies are the most common during development.
Referencing the package in the consuming app's pubspec.yaml
dependencies:
flutter:
sdk: flutter
# Local path dependency (monorepo / side-by-side folders)
my_ui_kit:
path: ../my_ui_kit
# OR — git dependency (specific branch)
# my_ui_kit:
# git:
# url: https://github.com/yourorg/my_ui_kit.git
# ref: main
After updating pubspec.yaml, run flutter pub get. You can now import and use any exported widget with a single import:
Using widgets from the shared library
import 'package:my_ui_kit/my_ui_kit.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
AvatarBadge(
imageUrl: 'https://example.com/user.jpg',
isOnline: true,
),
const SizedBox(height: 16),
StatusChip(status: StatusType.active),
const SizedBox(height: 24),
PrimaryButton(
label: 'Get Started',
onPressed: () => Navigator.pushNamed(context, '/onboarding'),
),
],
),
);
}
}
Versioning and Changelog Discipline
Every time you change the public API, bump the version in pubspec.yaml and add an entry to CHANGELOG.md. Follow Semantic Versioning:
- PATCH (1.0.0 → 1.0.1) — bug fixes, no API changes
- MINOR (1.0.0 → 1.1.0) — new backwards-compatible API
- MAJOR (1.0.0 → 2.0.0) — breaking changes (removed or renamed exports)
pub get. Always communicate breaking changes clearly in the CHANGELOG and bump the major version.Summary
Packaging widgets into a shared Dart library involves four key steps: scaffolding the package with flutter create --template=package, configuring the pubspec.yaml with correct SDK constraints and dependencies, organising source files under lib/src/ with a clean barrel file as the single public entry point, and consuming the package via a path, git, or hosted dependency. Following this pattern produces reusable, independently testable widget libraries that scale gracefully across multiple Flutter applications.