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
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',
)

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.