Components
Jaspr uses a component-based architecture very similar to Flutter's widgets. Concepts like ui composition, architecture and state management are transferable.
- StatelessComponent: For components that don't need mutable state. You must override
Component build(BuildContext context). - StatefulComponent: For components with mutable state. Requires an associated
Stateclass. The state has lifecycle methods likeinitState()anddispose(). You must overrideComponent build(BuildContext context)in the state class. - InheritedComponent: For propagating context or state efficiently down the component tree.
Returning Components from build
Building UIs in Jaspr requires you to return a single Component from build().
- Rule 1: You MUST NOT use
Iterable<Component> build(BuildContext context) sync*. This is legacy code. - Rule 2: You MUST use dot-shorthands instead of capitalized component names for fragments, text, and empty nodes.
- Use
.fragment([...])(Do NOT useFragment([...])orfragment([...])). - Use
.text('...')(Do NOT useText('...')ortext('...')). - Use
.empty()to return an empty space safely.
- Use
Example Usage:
import 'package:jaspr/jaspr.dart';
import 'package:jaspr/dom.dart';
class MyComponent extends StatelessComponent {
const MyComponent({super.key});
@override
Component build(BuildContext context) {
// 1. Return a single component (e.g. div)
// 2. Use dot-shorthand (.text) instead of Text()
return div(classes: 'my-class', [
.text('Hello World'),
]);
}
}
HTML Components
Jaspr provides typed components for standard HTML elements (e.g., div(), p(), a(), button()). To use these add the package:jaspr/dom.dart import.
All HTML components take standard named Key? key, String? id, String? classes, Styles? style, Map<String, String>? attributes and Map<String, void Function(Event)>? events parameters.
Most HTML components take a positional List<Component> children parameter (except for self-closing tags like img, input, br, etc.).
- Rule 1: ALWAYS put the
childrenlist LAST, after all named parameters. - Rule 2: You MUST prefer available typed parameters (e.g.,
href,src,onClick) over using the rawattributes:orevents:maps. - Rule 3: When you are unsure about which typed parameters exist for an HTML component, you MUST read the respective reference file provided alongside this skill:
references/html/<tag>.mdcontains the full signature and example usage of the component for the given tag. (e.g.references/html/div.mdfordiv(),references/html/button.mdforbutton(), etc.)
- Rule 4: When a respective reference file does not exist for a tag (and therefore the component itself doesn't exist), you MUST use the generic
.element(tag: '...', /* other standard params, */ children: [ /* ... */ ])constructor instead.
Example Usage:
import 'package:jaspr/jaspr.dart';
import 'package:jaspr/dom.dart';
class MyHtmlComponent extends StatelessComponent {
const MyHtmlComponent({super.key});
@override
Component build(BuildContext context) {
return div(id: 'my-container', classes: 'my-class', [
p(attributes: {'aria-label': 'Example Paragraph'}, [
.text('Hello World'),
]),
// E.g. signature as found at 'references/html/a.md'
a(href: 'https://example.com', [
.text('Click me'),
]),
]);
}
}
Styling Components
Jaspr has built-in support for styling components using CSS-in-Dart. See the jaspr-styling skill for more information.
Interactivity and Events
1. Accessing Browser APIs
- Rule 1: In server or static mode, you MUST use
package:universal_web/web.dartto access browser APIs andpackage:universal_web/js_interop.dartto access js interop APIs. - Rule 2: When using
package:universal_webin server or static mode, you MUST wrap all API calls in anif (kIsWeb)check to prevent crashing the server render. - Rule 3: In client mode, you can use
dart:js_interopandpackage:webdirectly. - Rule 4: For global events, you MUST use
web.EventStreamProvidersto listen to events on thewindowordocumentas they provide a typed DartStreamof events.
// Example of safe usage in server/static mode:
import 'package:universal_web/web.dart' as web;
void logSize() {
if (kIsWeb) {
print('Window size: ${web.window.innerWidth}x${web.window.innerHeight}');
// Example of global event listener
final sub = web.EventStreamProviders.resizeEvent.forTarget(web.window).listen((event) {
print('Window resized');
});
}
}
2. Handling Events
All DOM components support an events: parameter, and interactive components feature typed event callbacks (like onClick, onChange).
- Rule 1: You MUST use
web.Eventwhen typing raw events. - Rule 2: Use the
events()helper function for type-safe callback creation when needed.
import 'package:jaspr/jaspr.dart';
import 'package:jaspr/dom.dart';
import 'package:universal_web/web.dart' as web;
class MyButton extends StatelessComponent {
@override
Component build(BuildContext context) {
return div(
// Using raw events
events: {'click': (web.Event event) {
print('Div clicked');
}},
[
// Using typed parameters
button(
onClick: () => print('Button clicked'),
[.text('Click me')]
)
]
);
}
}
3. Accessing Elements (GlobalNodeKey)
If you need direct reference to an underlying DOM element rendered by Jaspr, you can assign it a GlobalNodeKey.
import 'package:jaspr/jaspr.dart';
import 'package:jaspr/dom.dart';
import 'package:universal_web/web.dart' as web;
class MyInput extends StatefulComponent {
@override
State<MyInput> createState() => _MyInputState();
}
class _MyInputState extends State<MyInput> {
final GlobalNodeKey<web.HTMLInputElement> inputKey = GlobalNodeKey();
void focusInput() {
inputKey.currentNode?.focus();
}
@override
Component build(BuildContext context) {
return input(key: inputKey, type: .text);
}
}
Further Resources
- For information on pre-rendering, async data fetching, hydration, and the
@clientannotation see the related skill: jaspr-pre-rendering-and-hydration. - For information on styling components see the related skill: jaspr-styling.
- For information on how to convert HTML to Jaspr code, see the related skill: jaspr-convert-html.
- An index of all available documentation can be found at https://jaspr.site/llms.txt.