Lark

Lark is a Dart web framework with:

  • file-based routing
  • fine-grained reactivity (ref, computed, effect)
  • composables for async/data/storage
  • static pre-rendering (SSG)
  • server-side rendering (SSR)
  • a built-in CLI for scaffolding, dev, and production builds

Layering Model

Lark separates core behavior and styled UI into two layers:

  • lark (package:lark/lark.dart) for headless, unstyled primitives and behavior.
  • lark_ui (package:lark_ui/lark_ui.dart) for opinionated, theme-aware UI components.

Canonical boundary spec: ../../docs/5.architecture/2.boundary.md

Install

dart pub add lark

Quick Start

Create a project:

dart run lark create my_app
cd my_app
dart pub get
lark dev

Open http://localhost:3000.

App Entry

lib/app.dart:

import 'package:lark/lark.dart';
import 'routes.g.dart';

final app = LarkApp(
  routes: routes,
  config: const LarkConfig(name: 'My App'),
  head: const Head(title: 'My App'),
);

web/main.dart:

import 'package:lark/lark.dart';
import 'package:my_app/app.dart';

void main() {
  createApp(app).mount('#app');
}

Dark Mode Setup

Set the Tailwind strategy in lib/app.dart (defaults to selector mode):

final config = LarkConfig(
  name: 'My App',
  tailwind: TailwindConfig(darkMode: DarkMode.selector),
);

Use dark: classes in UI and control preference with useDarkMode():

final theme = useDarkMode();
theme.toggle();      // switch dark/light
theme.setSystem();   // follow OS preference

On web, Lark automatically wires storage and system preference detection when the app mounts.

Reactivity

import 'package:lark/lark.dart';

class CounterPage extends Component {
  final count = ref(0);
  late final doubled = computed(() => count() * 2);

  @override
  Component build() {
    return Column([
      Text('Count: ${count()}'),
      Text('Doubled: ${doubled()}'),
      Button(label: 'Increment', onPressed: () => count.value++),
    ]);
  }
}

Routing

File-based routing

Lark generates lib/routes.g.dart from lib/pages:

  • lib/pages/index.dart -> /
  • lib/pages/about.dart -> /about
  • lib/pages/blog/__slug__/index.dart -> /blog/:slug

Routes are regenerated by lark dev, lark build, and lark codegen.

Runtime router APIs

  • useRouter() for push, replace, back, forward, go
  • useRoute() for current path/params/query/meta/data
  • useParams() and useQueryParams() helpers
  • RouterLink for SPA navigation
  • RouterOutlet for nested route trees

State, Forms, and Composables

Store

final useCounterStore = defineStore('counter', () {
  final count = ref(0);
  final doubled = computed(() => count() * 2);

  void increment() => count.value++;

  return (count: count, doubled: doubled, increment: increment);
});

Form

final form = useForm({
  'email': field(
    initialValue: '',
    validators: [required, email],
    formatters: [trim, lowercase],
  ),
  'password': field(validators: [required, minLength(8)]),
  'confirmPassword': field(initialValue: ''),
}, validators: [
  fieldsMatch('password', 'confirmPassword', message: 'Passwords must match'),
], autovalidateMode: AutovalidateMode.onUserInteraction);

final email = form.bindTextField('email', validateOnBlur: true);

Future<void> submit() async {
  final ok = await form.submit((values) async {
    await api.register(values);
  }, focusFirstError: true, scrollToFirstError: true);
  if (!ok) {
    print(form.errors);
    print(form.formError);
    print(form.submitError);
  }
}

Input.email(
  value: email.valueRef,
  onChanged: email.onChanged,
  onFocus: email.onFocus,
  onBlur: email.onBlur,
  onSubmit: email.onSubmit,
  textInputAction: TextInputAction.next,
);

final schemaForm = useSchemaForm({
  'fields': [
    {'name': 'email', 'validators': ['required', 'email']},
  ],
);

Overlays (Dialogs, Drawers, Popovers)

final open = ref(false);

Dialog(
  open: open,
  trigger: Button(label: 'Open settings'),
  content: DialogContent([
    DialogHeader([DialogTitle('Settings')]),
    DialogFooter([DialogClose(Button(label: 'Close'))]),
  ]),
  dismissOnEscape: true,
  dismissOnOutsideTap: true,
  trapFocus: true,
  lockScroll: true,
);

ScrollView Primitive

final scroll = ScrollController();

ScrollView(
  controller: scroll,
  axis: ScrollAxis.vertical, // vertical | horizontal | both
  showScrollbar: true,
  onScroll: (x, y) {
    // sync sticky UI, load-more signals, etc.
  },
  child: Column([
    Text('Scrollable content'),
  ]),
);

scroll.scrollToBottom();
scroll.scrollTo(y: 240, smooth: true);

Theme Tokens + DOM Helpers

final theme = Theme.light.copyWith(
  colors: Theme.light.colors.copyWith(primary: Color.hex('#2563eb')),
);
setTheme(theme, syncToDom: true);
final tailwindTokens = theme.toTailwindThemeExtension();

final dom = useDom();
dom.scrollToId('first-error', offset: 72);
dom.focusById('email');

Built-in composables

  • useFetch, useLazyFetch, usePost
  • usePagination, useInfiniteScroll, useAsync, usePolling
  • useToggle, useDebounce, useThrottle, usePrevious
  • useLocalStorage, useSessionStorage, useCookie, useDarkMode, useDom
  • useQuery, useMutation, useLazyQuery

SEO and Head Tags

  • useMeta(...)
  • useSeo(...)
  • useHead([...])
  • app-level Head(...) on LarkApp

Content System

Put content files in lib/content (.md, .yaml, .json) and run codegen.

Generated catalog: lib/content/content.g.dart

final posts = await content.query('blog').search('dart').find();
final one = await content.findOne('my-post-slug');

CLI

lark create <name>
lark dev [--port] [--verbose] [--ssr]
lark build [--wasm | --js]
lark start
lark serve [--port] [--host] [--dir]
lark generate <component|page|store> <name>
lark codegen
lark doctor
lark clean
lark tailwind setup

Pub Score Check

Run the local pub score check with the included helper:

./tool/pana_check.sh

This script currently forces --dartdoc-version "<9.0.0" as a temporary workaround for a pana/dartdoc compatibility issue on Dart 3.11.

Deployment

Static (SPA + pre-rendered pages)

lark build

Deploy build/ to static hosting.

Local check:

lark serve

SSR

lark build also compiles an SSR server binary:

lark build
lark start

lark start runs build/server (host/port from args or .env).

For source-mode SSR in environments with Dart installed:

cd build
dart pub get
dart run bin/server.dart

Split Imports

Use smaller entrypoints when needed:

  • package:lark/core.dart
  • package:lark/router.dart
  • package:lark/store.dart
  • package:lark/seo.dart
  • package:lark/composables.dart
  • package:lark/ssr.dart
  • package:lark/web.dart

Libraries

composables
Lark Composables — Reusable reactive utilities.
core
Lark Core
dev
Lark Dev — Development tools, logging, error boundaries.
lark
Primary Lark framework entrypoint.
router
Lark Router — File-based routing system.
seo
Lark SEO — Meta tags, OpenGraph, structured data.
ssr
Server-Side Rendering module for Lark.
store
Lark Store — Pinia-style state management.
web
Web-specific entry point for Lark applications.