| name | jaspr-fundamentals |
| description | Use when working in a Jaspr project, on Jaspr components, or other Jaspr-related tasks. Contains fundamentals of writing Jaspr components and using HTML components. |
| metadata | {"jaspr_version":"0.23.0"} |
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
State class. The state has lifecycle methods like initState() and dispose(). You must override Component 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 use Fragment([...]) or fragment([...])).
- Use
.text('...') (Do NOT use Text('...') or text('...')).
- Use
.empty() to return an empty space safely.
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
children list LAST, after all named parameters.
- Rule 2: You MUST prefer available typed parameters (e.g.,
href, src, onClick) over using the raw attributes: or events: 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>.md contains the full signature and example usage of the component for the given tag. (e.g. references/html/div.md for div(), references/html/button.md for button(), 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.dart to access browser APIs and package:universal_web/js_interop.dart to access js interop APIs.
- Rule 2: When using
package:universal_web in server or static mode, you MUST wrap all API calls in an if (kIsWeb) check to prevent crashing the server render.
- Rule 3: In client mode, you can use
dart:js_interop and package:web directly.
- Rule 4: For global events, you MUST use
web.EventStreamProviders to listen to events on the window or document as they provide a typed Dart Stream of 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.Event when 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