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->/aboutlib/pages/blog/__slug__/index.dart->/blog/:slug
Routes are regenerated by lark dev, lark build, and lark codegen.
Runtime router APIs
useRouter()forpush,replace,back,forward,gouseRoute()for current path/params/query/meta/datauseParams()anduseQueryParams()helpersRouterLinkfor SPA navigationRouterOutletfor 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,usePostusePagination,useInfiniteScroll,useAsync,usePollinguseToggle,useDebounce,useThrottle,usePrevioususeLocalStorage,useSessionStorage,useCookie,useDarkMode,useDomuseQuery,useMutation,useLazyQuery
SEO and Head Tags
useMeta(...)useSeo(...)useHead([...])- app-level
Head(...)onLarkApp
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.dartpackage:lark/router.dartpackage:lark/store.dartpackage:lark/seo.dartpackage:lark/composables.dartpackage:lark/ssr.dartpackage: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.