prism_kit 1.1.0
prism_kit: ^1.1.0 copied to clipboard
A unified Flutter component library with a shared design system: adaptive toasts, banners, sheets, data tables, steppers, skeletons, avatars, OTP and chip inputs, timelines, a command palette, search, [...]
π· Prism
One dependency. 27 adaptive Flutter components. Native feel on phone, desktop & web.
Prism is a component library for Flutter that removes the most repetitive parts of app building. Instead of assembling a dozen fragmented pub.dev packages β each with its own style, API and quirks β you install one package and get a coherent, cross-platform toolkit with a shared design system:
- Adaptive by default β every component responds to window size and target platform. A toast is bottom-centered on a phone and stacked top-right on a desktop window. A settings page becomes a two-pane layout on tablets. A dialog renders Material on Android and Cupertino-style behavior on macOS β from a single call.
- Local-first & privacy-friendly β persistence uses
shared_preferenceson-device; no accounts, no telemetry, no network calls unless you explicitly opt in (the AI client and updater are the only networked parts, and you own the endpoints). - A real design system β
PrismTokens(spacing, radii, motion) andPrismColors(semantic roles) so every component shares one visual language and follows light/dark and Material You automatically. - Zero heavy dependencies β only
http,markdown,shared_preferences,url_launcher,metaandmaterial_color_utilities. - Battle-tested β 385 unit & widget tests, analyzed clean.
π¦ Install #
dependencies:
prism_kit: ^1.0.0
flutter pub add prism_kit
One import gives you everything:
import 'package:prism_kit/prism.dart';
π§© Components #
| # | Component | One-liner |
|---|---|---|
| 1 | AdaptiveScaffold | Bottom nav bar β navigation rail β expanded rail, by window width |
| 2 | AdaptiveDialog | Platform-adaptive dialogs from a single call |
| 3 | PrismAi | Provider-agnostic streaming AI chat: OpenAI, Ollama, Groq, DeepSeek, Gemini, Anthropic β with typed events, tool-calling loop and a ready chat view |
| 4 | Banner | Dismissible announcement banners; dismissals persist across restarts |
| 5 | Changelog | Parse a Keep-a-Changelog markdown file into collapsible "What's new" cards |
| 6 | CodeView | Syntax-highlighted, copyable code viewer with pluggable tokenizers |
| 7 | Form | Declarative forms: fields, validators, submit lifecycle states |
| 8 | Image | Retrying network image provider with configurable backoff strategy |
| 9 | Markdown | Fast, plugin-free CommonMark + GFM renderer with syntax highlighting |
| 10 | MasterDetail | Responsive two-pane layouts (list β detail) that push on compact windows |
| 11 | Pagination | Infinite-scroll list with page loader, empty/error/retry states |
| 12 | Palette | Extract dominant/vibrant/muted color palettes from images |
| 13 | Search | Adaptive search: local datasets or debounced async backends |
| 14 | Settings | iOS-style grouped settings pages with searchable tiles |
| 15 | StateViews | Loading / empty / error / success state widgets driven by one notifier |
| 16 | Toast | Adaptive toasts: bottom-centered on mobile, stacked top-right on desktop |
| 17 | Updater | In-app update flow: version gates, rollouts, skip-version, download progress |
| 18 | Skeleton | Shimmering placeholders: text, card, tile list, circle β reduced-motion aware |
| 19 | Avatar | Image with deterministic initials fallback, presence dots and badges |
| 20 | ChipInput | Tag/token entry with paste splitting, duplicates, limits and suggestions |
| 21 | OtpField | OTP/PIN entry with paste, auto-submit, SMS autofill and grouping |
| 22 | Timeline | Vertical event timeline with connectors, semantic roles and rich nodes |
| 23 | Stepper | Wizard progress: horizontal on wide, compact "Step X of Y" on phones |
| 24 | DataTable | Sortable table that becomes labelled cards on narrow windows |
| 25 | CommandPalette | βK launcher with fuzzy search, grouping and keyboard navigation |
| 26 | AdaptiveSheet | Bottom sheet on phones, centred dialog on desktop β one call |
| 27 | ConnectionBanner | Offline/online banner with debounce, retry and live-region announcements |
Design system #
| PrismTokens | Spacing scale, corner radii, motion durations and breakpoints, installable via ThemeData.extensions |
| PrismColors | Semantic roles (success / warning / error / info) plus raised and sunken surfaces, derived from any ColorScheme |
π Usage #
Every snippet below is copy-pasteable and matches the shipped API.
AdaptiveScaffold β responsive navigation #
Renders a NavigationBar under 600 dp, a NavigationRail up to 839 dp and an expanded labeled rail beyond:
PrismAdaptiveScaffold(
destinations: const [
AdaptiveDestination(icon: Icons.home_outlined, selectedIcon: Icons.home, label: 'Home'),
AdaptiveDestination(icon: Icons.search_outlined, selectedIcon: Icons.search, label: 'Search'),
AdaptiveDestination(icon: Icons.settings_outlined, label: 'Settings'),
],
bodyBuilder: (index) => const Center(child: Text('Page')),
)
Toast β adaptive notifications #
PrismToast.success(context, 'Saved successfully');
PrismToast.error(context, 'Something went wrong');
PrismToast.info(context, 'Heads up!');
PrismToast.warning(context, 'Low battery');
// Full control
PrismToast.show(
context,
const PrismToastMessage(
message: 'Report synced',
title: 'Done',
duration: Duration(seconds: 5),
actionLabel: 'View',
onAction: openReport,
),
);
Position adapts automatically: bottom-centered on phones, stacked top-right where there's room.
Banner β dismissible announcements #
Column(
children: [
PrismBanner(
announcements: const [
PrismAnnouncement(
id: 'release-2-1', // reuse the id + bump version to re-show
version: 2,
title: 'New in 2.1',
message: 'Dark mode and faster sync!',
actionLabel: "See what's new",
severity: PrismSeverity.info,
),
],
),
// β¦the rest of your pageβ¦
],
)
Dismissals persist via shared_preferences. Banners can be scheduled (startsAt/endsAt) and prioritized (newest, oldest, severity).
Search β local or backend #
// Local dataset
PrismSearch<String>(
items: const ['Dart', 'Flutter', 'Firebase'],
itemLabel: (item) => item,
onSelected: (item) => print('picked $item'),
)
// Debounced async backend
PrismSearch<City>(
onSearch: (query) => api.searchCities(query),
itemLabel: (city) => city.name,
onSelected: (city) => go(city),
)
Settings β grouped, searchable pages #
final controller = PrismSettingsController();
PrismSettingsPage(
controller: controller,
searchable: true,
sections: const [
PrismSettingsSection(
title: 'Appearance',
tiles: [
PrismSwitchTile(
common: PrismTileCommon(
key: 'dark-mode',
title: 'Dark mode',
subtitle: 'Follow the system theme',
leading: Icons.dark_mode,
),
),
PrismActionTile(
common: PrismTileCommon(
key: 'language',
title: 'Language',
subtitle: 'English',
leading: Icons.language,
),
onTap: openLanguagePicker,
),
],
),
],
)
Read & write values through the controller: controller.set('dark-mode', true) / controller.get<bool>('dark-mode').
Markdown + CodeView β rich content #
PrismMarkdown('# Hello\n\n- **Rich** markdown\n- `inline code`')
PrismCodeView(code: "void main() => print('Hello');")
The markdown renderer is plugin-free (pure Dart CommonMark + GFM) and pairs perfectly with ChangelogView bullets.
Form β declarative & validated #
final controller = PrismFormController();
PrismForm(
controller: controller,
child: Column(children: [
PrismTextField.email(name: 'email', label: 'Email'),
PrismPasswordField(name: 'password', common: PrismFieldCommon(
name: 'password', label: 'Password',
validators: [Validators.required('Enter a password')],
)),
PrismDropdownField<String>(
common: PrismFieldCommon(name: 'plan', label: 'Plan'),
items: const ['Free', 'Pro'],
itemBuilder: (plan) => plan,
),
FilledButton(
onPressed: () async {
if (await controller.submit((values) => api.register(values))) {
// success β status transitions surfaced via controller
}
},
child: const Text('Create account'),
),
]),
)
Validators come from Validators.required(), Validators.email() etc., or you can pass any (value) => messageOrNull function. Submit runs all validations, then your action, and exposes typed statuses (pristine, editing, submitting, success, failure).
StateViews β one widget, four states #
final notifier = PrismViewStateNotifier<List<Note>>(const PrismViewLoading());
PrismStateView(
state: notifier.value,
successBuilder: (notes) => NotesList(notes),
emptyMessage: 'No notes yet',
)
Transition with notifier.value = PrismViewSuccess(data) / PrismViewError(error) / PrismViewEmpty().
Pagination β infinite scroll #
PrismPaginatedList<Msg>(
pageLoader: (page) => api.fetchMessages(page), // return [] to signal end
itemBuilder: (context, msg, index) => MsgTile(msg),
)
First-load spinner, error state with retry, and "no more items" footer are handled for you.
MasterDetail β two-pane layouts #
PrismMasterDetail<Conversation>(
items: conversations,
masterBuilder: (context, conv, selected) => ConvTile(conv, selected: selected),
detailBuilder: (context, conv) => ChatScreen(conv),
)
On compact windows it pushes a detail route (PrismCompactBehavior.push); on wide windows it becomes a side-by-side split with a configurable pane width.
Palette β extract colors from images #
final palette = await PrismPalette.fromImage(image);
final themeColor = palette.vibrantColor ?? palette.dominantColor;
AI β streaming chat with any provider #
final controller = AiChatController(
client: OpenAiStreamClient(
baseUrl: 'https://api.openai.com/v1', // or http://localhost:11434/v1 (Ollama)
apiKey: 'sk-β¦',
),
model: 'gpt-4o-mini',
);
AiChatView(controller: controller) // full streaming chat UI
Typed events (AiDelta, AiToolCall, AiDone, AiError), SSE parsing, and an automatic tool-calling agent loop (AiAgent) are included. Swap in GeminiStreamClient, AnthropicStreamClient, or implement AiStreamClient for anything else.
Updater β in-app updates #
PrismUpdateController(
source: RemoteUpdateSource(url: 'https://example.com/updates.json'),
// updates.json: {"versions": {"1.1.0": {"notes": "...", "url": "..."}}}
)
Handles version comparison, staged rollouts, skip-version persistence and download progress events.
ChangelogView β What's new #
ChangelogView(
changelog: await rootBundle.loadString('CHANGELOG.md'),
highlightVersion: '2.1.0',
)
Parses Keep-a-Changelog format into collapsible version cards; WhatsNewSheet shows it as a modal.
Skeleton β loading that keeps the page shape #
if (loading)
const PrismSkeletonList.cards(count: 3)
else
NoteList(notes)
// Or compose primitives
PrismSkeletonCard(mediaHeight: 140, lines: 3)
PrismSkeletonText(lines: 2, lastLineFraction: 0.4)
PrismSkeleton.circle(diameter: 44)
The shimmer stops automatically when the platform requests reduced motion, so you never ship a strobe to a user who asked for calm.
Avatar β never a broken image #
PrismAvatar(name: 'Ayesha Khan')
PrismAvatar(name: 'Ayesha Khan', imageUrl: user.photoUrl, size: 56)
PrismAvatar(name: 'Ayesha Khan', presence: PrismPresence.online)
PrismAvatar(name: 'Acme Inc', shape: PrismAvatarShape.rounded, size: 48)
Initials come from PrismAvatar.initialsFor(name); the fallback colour is picked deterministically from the name, so a person looks the same on every device. Failed image loads silently fall back to initials.
ChipInput β tags without the pain #
PrismChipInput(
label: 'Tags',
value: tags,
suggestions: const ['dart', 'flutter', 'ui'],
maxChips: 5,
helperText: 'Paste a comma-separated list to add several at once.',
onChanged: (next) => setState(() => tags = next),
)
Handles comma/newline/semicolon paste, duplicate rejection (case-insensitive), backspace-to-delete, custom validators and an optional suggestion overlay.
OtpField β paste, autofill, auto-submit #
PrismOtpField(
length: 6,
groupSeparatorAfter: 3, // renders 123 456
errorText: error,
onCompleted: (code) => verify(code),
)
PrismOtpField(length: 4, obscure: true) // PIN entry
A single transparent field sits behind the digit boxes, so IME, paste, selection, autofill (oneTimeCode) and screen readers all behave natively.
Timeline β order tracking and activity feeds #
PrismTimeline(
entries: [
PrismTimelineEntry(
title: 'Order placed',
timestamp: '09:12',
semantic: PrismSemantic.success,
),
PrismTimelineEntry(
title: 'Out for delivery',
subtitle: 'Driver Sam Β· 2 stops away',
isActive: true,
child: TrackLiveCard(),
),
PrismTimelineEntry(
title: 'Delivery failed',
semantic: PrismSemantic.error,
),
],
onEntryTap: (index, entry) => open(index),
)
Stepper β wizards that stay readable #
PrismStepper(
steps: const [
PrismStep(title: 'Account'),
PrismStep(title: 'Profile'),
PrismStep(title: 'Preferences', isOptional: true),
],
currentIndex: step,
erroredIndices: {1},
onStepTapped: (i) => setState(() => step = i),
)
Horizontal with connectors on wide windows; a compact Step 2 of 3 header with a progress bar on phones.
DataTable β one widget, two layouts #
PrismDataTable<Invoice>(
rows: invoices,
onRowTap: (row, i) => open(row),
columns: [
PrismColumn(label: 'Invoice', alwaysVisible: true,
cell: (c, r, i) => Text(r.id),
sortComparator: (a, b) => a.id.compareTo(b.id)),
PrismColumn(label: 'Client', cell: (c, r, i) => Text(r.client)),
PrismColumn(label: 'Total', numeric: true, cell: (c, r, i) => Text('\$${r.total}')),
],
)
Desktop gets a sortable table with a header row; phones get the same data as cards with field labels. No horizontal scrolling, no hidden columns.
CommandPalette β a βK launcher #
final chosen = await PrismCommandPalette.show(
context: context,
commands: [
PrismCommand(label: 'New note', icon: Icons.add, group: 'File',
shortcut: 'βN', onSelect: createNote),
PrismCommand(label: 'Toggle theme', group: 'View',
keywords: const ['dark', 'appearance'], onSelect: toggleTheme),
],
);
Fuzzy subsequence search (so tt finds Toggle theme), grouping, arrow/Enter/Escape navigation, and a full-screen route on phones.
AdaptiveSheet β bottom sheet or dialog, automatically #
final confirmed = await PrismAdaptiveSheet.show<bool>(
context: context,
title: 'Delete this note?',
subtitle: 'It will move to Trash for 30 days.',
child: const Text('You can restore it any time.'),
actions: [
const PrismSheetAction(label: 'Cancel', value: false),
PrismSheetAction(label: 'Delete', value: true, isDestructive: true),
],
);
ConnectionBanner β offline UX in one line #
PrismConnectionBanner(
source: () => Connectivity().onConnectivityChanged.map((r) => r != ConnectivityResult.none),
onlineMessage: 'Back online β syncingβ¦',
onRetry: refetch,
)
Dampens short blips with a settle delay, flashes a reconnected message, and announces changes through a polite live region.
Design tokens β one visual language #
MaterialApp(
theme: ThemeData(
colorSchemeSeed: Colors.teal,
extensions: const [
PrismTokens(spaceMd: 20, radiusMd: 16, durationMedium: Duration(milliseconds: 200)),
],
),
)
// Anywhere:
Padding(padding: EdgeInsets.all(context.prismTokens.spaceMd), child: β¦)
Container(color: context.prismColors.surfaceRaised)
Container(color: context.prismColors.forRole(PrismSemantic.success).background)
Every component reads these tokens, so one override restyles the whole app consistently.
π₯ Platform support #
| Component | Android | iOS | macOS | Windows | Linux | Web |
|---|---|---|---|---|---|---|
| AdaptiveScaffold / MasterDetail / Dialog | β | β | β | β | β | β |
| Toast / Banner / StateViews / Pagination | β | β | β | β | β | β |
| Search / Settings / Form | β | β | β | β | β | β |
| Markdown / CodeView / Changelog / Palette | β | β | β | β | β | β |
| Image (NetworkImageWithRetry) | β | β | β | β | β | β |
| AI client | β | β | β | β | β | β |
| Updater | β | β | β | β | β | β |
| Skeleton / Avatar / ChipInput / OtpField | β | β | β | β | β | β |
| Timeline / Stepper / DataTable | β | β | β | β | β | β |
| CommandPalette / AdaptiveSheet / ConnectionBanner | β | β | β | β | β | β |
| PrismTokens / PrismColors | β | β | β | β | β | β |
π§ͺ Quality #
- 385 tests (unit + widget) across all components
- Zero analyzer issues;
flutter_lintsenforced - Every public API documented
π§ Migrating from the standalone packages #
If you previously used an individual prism_* package, swap the dependency and change one import:
- prism_toast: ^0.1.0
+ prism_kit: ^1.0.0
- import 'package:prism_toast/prism_toast.dart';
+ import 'package:prism_kit/prism.dart';
All class names stay identical (PrismToast, PrismBanner, PrismSearch, β¦).
π Full example #
The example/ folder contains a gallery app with an interactive demo for every component β run it with:
cd example && flutter run
π€ Contributing #
Issues and PRs welcome β pick a component and improve it: github.com/awaisdev5765/Prism.
β Support #
If Prism helps you ship faster, please consider supporting my work:
π License #
MIT β see LICENSE.