Flint UI
flint_ui is a Dart-first web UI layer for Flint fullstack applications. It gives you a small component model, browser rendering, page mounting, typed style helpers, browser storage, navigation utilities, and a growing set of application components.
The goal is simple: build real FlintDart web interfaces in Dart without reaching for raw HTML, JavaScript, and CSS strings for every screen.
Status
Flint UI is pre-1.0 and actively evolving. The current package is suitable for building FlintDart app UIs and internal dashboards, but APIs may still change while the v1 surface is shaped.
Install
Add flint_ui to your app:
dependencies:
flint_ui: ^0.1.3
For local Flint development inside this monorepo, use a path override:
dependency_overrides:
flint_ui:
path: ../flint_ui
Import the full UI package:
import 'package:flint_ui/flint_ui.dart';
For lower-level tests or package internals, you can import:
import 'package:flint_ui/flint_ui_core.dart';
Quick Start
Create a Flint UI entry file, usually flint_ui/main.dart:
import 'package:flint_ui/flint_ui.dart';
void main() {
createFlintApp(
'#app',
pages: {
'Home': (_) => HomePage(),
},
);
}
class HomePage extends FlintComponent {
@override
FlintNode build() {
return Container(
dartStyle: const DartStyle(
padding: EdgeInsets.all(24),
maxWidth: SizeValue.rem(48),
),
children: [
Text.h1('Hello Flint UI'),
Text.p('Build browser UI with Dart components.'),
Button(
child: 'Continue',
onPressed: (_) {
navigation.assign('/dashboard');
},
),
],
);
}
}
Render the page from FlintDart:
Response home(Request req, Response res) {
return res.page(
'Home',
title: 'My Flint App',
script: '/main.dart.js',
props: {
'name': 'Flint',
},
);
}
Compile the UI:
dart compile js flint_ui/main.dart -o public/main.dart.js
When used through FlintDart hot reload, Flint can compile the UI bundle and refresh the browser for you.
Components
Every UI component is a FlintComponent that returns a FlintNode.
class Counter extends FlintComponent {
int value = 0;
@override
FlintNode build() {
return Row(
dartStyle: const DartStyle(
display: Display.flex,
gap: 12,
alignItems: AlignItems.center,
),
children: [
Text.span('Count: $value'),
Button(
child: 'Add',
onPressed: (_) {
setState(() => value++);
},
),
],
);
}
}
Lifecycle hooks are available:
class Example extends FlintComponent {
@override
void didMount() {}
@override
void didUpdate() {}
@override
void willUnmount() {}
@override
FlintNode build() => Text('Example');
}
Basic Nodes
You can use high-level components:
Column(
children: [
Text.h2('Account'),
Text.p('Manage your profile.'),
],
)
Or create raw elements:
h(
'section',
props: {'class': 'panel'},
children: [
text('Raw element'),
],
)
Common helpers include:
div()
span()
button()
input()
text()
fragment()
component()
toFlintNode()
Pages And Registry
For multi-page apps, use FlintComponentRegistry:
final componentRegistry = FlintComponentRegistry({
'Login': (props) => LoginPage(props),
'Dashboard': (props) => DashboardPage(props),
});
void main() {
createFlintApp(
'#app',
registry: componentRegistry,
);
}
A page receives server props through data-flint-page:
class DashboardPage extends FlintComponent {
DashboardPage(this.props);
final Map<String, dynamic> props;
@override
FlintNode build() {
final role = props['role']?.toString() ?? 'customer';
return Text.h1('Dashboard: $role');
}
}
Page Middleware
Client-side page middleware can stop rendering or redirect before a page mounts:
const session = AuthSessionManager(
tokenKey: 'app.token',
userKey: 'app.user',
);
void requireAuth(FlintPageContext context) {
if (context.page.component != 'Dashboard') return;
if (session.isLoggedIn) return;
navigation.redirect('/login');
context.stop();
}
void main() {
createFlintApp(
'#app',
registry: componentRegistry,
middlewares: [requireAuth],
);
}
For real protection, also guard the server route in FlintDart.
Styling
Flint UI supports plain style maps and the typed DartStyle API.
Container(
dartStyle: const DartStyle(
padding: EdgeInsets.all(24),
background: Colors.white,
color: Colors.slate900,
radius: 12,
border: Border.all(color: Colors.slate200),
shadow: Shadow(
y: 16,
blur: 40,
color: Color.rgba(15, 23, 42, 0.12),
),
),
child: Text('Styled panel'),
)
Size Values
Numbers become pixels by default:
DartStyle(width: 320) // width: 320px
Use SizeValue when you want another unit:
const DartStyle(
width: SizeValue.full,
maxWidth: SizeValue.rem(48),
height: SizeValue.auto,
)
Available helpers:
SizeValue.px(12)
SizeValue.percent(50)
SizeValue.rem(4)
SizeValue.em(2)
SizeValue.auto
SizeValue.full
Colors
Use CSS strings, Color, or predefined Colors:
const DartStyle(color: '#0f172a')
const DartStyle(color: Color.rgb(15, 23, 42))
const DartStyle(color: Color.rgba(37, 99, 235, 0.9))
const DartStyle(color: Colors.blue600)
Current named colors include:
Colors.white
Colors.black
Colors.transparent
Colors.slate50 ... Colors.slate900
Colors.blue50 ... Colors.blue900
Colors.sky50 ... Colors.sky900
Colors.cyan50
Colors.cyan700
Colors.rose50
Colors.rose200
Colors.rose700
Gradients
Use predefined gradients:
const DartStyle(gradient: Gradients.ocean)
const DartStyle(gradient: Gradients.sky)
const DartStyle(gradient: Gradients.softPanel)
Or build custom gradients:
DartStyle(
gradient: Gradient.linear(135, const [
GradientStop(Colors.sky500, 0),
GradientStop(Colors.blue600, 58),
GradientStop(Colors.blue700, 100),
]),
)
For evenly distributed colors:
DartStyle(
gradient: Gradient.linearColors(135, const [
Colors.blue600,
Colors.sky500,
]),
)
Flex
Use Flex helpers instead of raw CSS strings:
const DartStyle(
display: Display.flex,
flex: Flex.fill(),
)
Available helpers:
Flex.fill() // 1 1 auto
Flex.auto() // 1 1 auto
Flex.grow() // 1 1 0%
Flex.none() // 0 0 auto
Flex(2, 0, SizeValue.auto)
Individual properties are also available:
const DartStyle(
flexGrow: 1,
flexShrink: 0,
flexBasis: SizeValue.auto,
)
Responsive Styles
DartStyle supports responsive breakpoint overrides:
const DartStyle(
width: SizeValue.full,
padding: EdgeInsets.all(16),
md: DartStyle(
width: 520,
padding: EdgeInsets.all(32),
),
lg: DartStyle(
width: 640,
),
)
Breakpoints:
sm: 640px
md: 768px
lg: 1024px
xl: 1280px
Responsive styles are compiled into scoped CSS rules by mergeComponentProps.
Style Sheets
For reusable CSS classes, use StyleSheet and StyleRule:
const appStyles = StyleSheet(
'app',
{
'.button': StyleRule({
'background': Colors.blue600,
'color': Colors.white,
'border-radius': '8px',
}, hover: {
'background': Colors.blue700,
}),
},
);
Register stylesheets when the app starts:
createFlintApp(
'#app',
registry: componentRegistry,
stylesheets: [appStyles],
);
Then use generated class names:
Button(
className: appStyles.className('button'),
child: 'Save',
)
Component Catalog
These are the main public component groups exported by package:flint_ui/flint_ui.dart.
Primitives
Container
Row
Column
Text
Link
Image
Figure
Actions
Button
ButtonGroup
IconButton
Forms
Form
TextField
TextArea
Select
Checkbox
RadioGroup
Switch
FileInput
FieldGroup
TextEditingController
FormController
Layout
AppShell
Grid
Section
Panel
PageHeader
Sidebar
Spacer
Stack
StatCard
Topbar
Wrap
Divider
EmptyState
Data
Avatar
DataTable
DescriptionList
ProgressBar
Table
Timeline
UsageMeter
Feedback
Alert
Spinner
StatusBadge
Navigation
Breadcrumbs
Pagination
SearchBox
Tabs
Overlays
ConfirmAction
Drawer
Modal
Popover
Skeleton
Toast
Tooltip
Images
Image(
src: '/images/avatar.png',
alt: 'User avatar',
width: 80,
height: 80,
loading: ImageLoading.lazy,
decoding: ImageDecoding.async,
)
With a caption:
Figure(
image: Image(
src: '/images/server.png',
alt: 'Server rack',
),
caption: 'Primary hosting node',
)
Navigation
Use the navigation singleton:
navigation.assign('/dashboard');
navigation.redirect('/login');
navigation.replace('/settings');
navigation.back();
navigation.reload();
Read the current URL:
currentUrl
currentPath
currentQuery
currentHash
currentUri
Example active sidebar:
Sidebar(
items: const [
SidebarItem(label: 'Overview', href: '/dashboard'),
SidebarItem(label: 'Settings', href: '/dashboard/settings'),
],
activePath: currentUri.path,
)
Query Parameters
Use the query helper when you need to read or update browser query state without manually parsing window.location.search.
final tab = query.get('tab');
query.set('tab', 'billing');
query.update({
'page': 2,
'filter': 'active',
});
query.remove('filter');
Browser Storage
Flint UI provides a small browser storage abstraction:
localStorage.write('theme', 'dark');
final theme = localStorage.read('theme');
localStorage.remove('theme');
Session storage:
sessionStorage.write('draft', 'hello');
Cookies:
cookies.write(
'auth.token',
token,
maxAge: const Duration(days: 7),
sameSite: CookieSameSite.lax,
);
final token = cookies.read('auth.token');
cookies.remove('auth.token');
Auth Session
AuthSessionManager stores an auth token and user object in browser storage:
const authSession = AuthSessionManager(
tokenKey: 'app.token',
userKey: 'app.user',
);
authSession.save(
token: token,
user: {
'id': 1,
'email': 'admin@example.com',
'role': 'admin',
},
);
if (authSession.isLoggedIn) {
navigation.assign('/dashboard');
}
authSession.clear();
By default it uses localStorage. You can provide another storage implementation:
const sessionAuth = AuthSessionManager(
storage: sessionStorage,
);
HTTP Client
Use clientRouter to call FlintDart APIs from the browser:
final response = await clientRouter.group('/auth').post<Map<String, dynamic>>(
'/login',
body: {
'email': email,
'password': password,
},
);
if (response.isError) {
throw response.error!;
}
final data = response.data;
When running in the browser, ClientRouter defaults to the current browser origin if no base URL is provided.
Environment Config
Use EnvironmentConfig for browser-side configuration:
final apiBase = env.get('API_BASE_URL', fallback: '/api');
This is useful when the server injects public configuration into the page.
FlintDart Integration
A typical FlintDart fullstack UI route looks like this:
class UiController {
Response login(Request req, Response res) {
return res.page(
'Login',
title: 'Login',
script: '/main.dart.js',
props: {
'authBase': '/auth',
},
);
}
}
And the browser entrypoint:
void main() {
createFlintApp(
'#app',
registry: FlintComponentRegistry({
'Login': (props) => LoginPage(props),
'Dashboard': (props) => DashboardPage(props),
}),
);
}
Development
Install dependencies:
dart pub get
Run tests:
dart test -p chrome
Run focused tests:
dart test test/style_test.dart -p chrome
dart test test/widgets_test.dart -p chrome
Analyze:
dart analyze
Generate API documentation:
dart doc
The generated API reference is written to doc/api/index.html.
Format:
dart format lib test
Package Structure
lib/
flint_ui.dart # Full public export
flint_ui_core.dart # Core public export
src/
auth/ # Auth session helpers
client/ # Browser API client
config/ # Browser environment config
navigation/ # Browser navigation/query helpers
storage/ # localStorage/sessionStorage/cookies
style.dart # Style public entrypoint
style/ # Split style modules
widgets/ # Component library
Design Direction For V1
Flint UI v1 should make it possible to build production admin apps, dashboards, auth flows, forms, and operational tools with Dart-first components.
Priority areas:
Typed style system
Responsive layout primitives
Core form controls
Feedback and loading states
Tables and data views
App shells and navigation
Better examples and docs
Stable FlintDart fullstack workflow
License
MIT
Libraries
- flint_ui
- Browser entrypoint APIs for building Flint UI applications.
- flint_ui_core
- Core component, node, style, storage, navigation, and widget APIs.