Skip to main content
Components are the central building blocks in Jaspr. They are immutable descriptions of parts of your user interface that get inflated into elements which manage the underlying DOM.

Component Types

Jaspr provides three core component types, each serving a different purpose:

StatelessComponent

A StatelessComponent is used when your UI doesn’t need to maintain any mutable state. It’s the simplest component type and is ideal for static content or components that only depend on their constructor parameters.
import 'package:jaspr/jaspr.dart';

class Hello extends StatelessComponent {
  const Hello({required this.name, super.key});

  final String name;

  @override
  Component build(BuildContext context) {
    return div([
      p([text('Hello $name')]),
    ]);
  }
}
Key characteristics:
  • All fields must be final
  • No mutable state
  • The build method is called when:
    • The component is first inserted into the tree
    • The parent changes the component’s configuration
    • An InheritedComponent it depends on changes
When to use:
  • Static content that doesn’t change
  • Components that only transform input parameters into UI
  • Presentational components without internal state

StatefulComponent

A StatefulComponent is used when your UI needs to maintain mutable state that can change over time. The component itself remains immutable, but it creates a separate State object to hold mutable data.
import 'package:jaspr/jaspr.dart';

class Counter extends StatefulComponent {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int count = 0;

  @override
  Component build(BuildContext context) {
    return div([
      p([text('Count: $count')]),
      button(
        events: {
          'click': (_) {
            setState(() => count++);
          },
        },
        [text('Increment')],
      ),
    ]);
  }
}
Key characteristics:
  • Component class is immutable and creates a State object
  • State object holds mutable data
  • Use setState() to update state and trigger a rebuild
  • Lifecycle methods available: initState, didChangeDependencies, didUpdateComponent, dispose
State lifecycle:
  1. createState() - Creates the State object
  2. initState() - Called once when the State is created
  3. didChangeDependencies() - Called after initState and when dependencies change
  4. build() - Called to render the UI (can be called multiple times)
  5. didUpdateComponent() - Called when the parent rebuilds with a new component
  6. deactivate() - Called when removed from the tree
  7. dispose() - Called when permanently removed
When to use:
  • Forms and user input
  • Interactive UI elements (buttons, toggles, etc.)
  • Components that need to respond to user events
  • Components with animations or timers

InheritedComponent

An InheritedComponent efficiently propagates data down the component tree. Descendant components can access this data and automatically rebuild when it changes.
import 'package:jaspr/jaspr.dart';

class ThemeData {
  const ThemeData({required this.primaryColor});
  final String primaryColor;
}

class Theme extends InheritedComponent {
  const Theme({
    required this.data,
    required super.child,
    super.key,
  });

  final ThemeData data;

  // Provide a convenient static method to access the theme
  static ThemeData of(BuildContext context) {
    final theme = context.dependOnInheritedComponentOfExactType<Theme>();
    return theme?.data ?? const ThemeData(primaryColor: 'blue');
  }

  @override
  bool updateShouldNotify(Theme oldComponent) {
    return data.primaryColor != oldComponent.data.primaryColor;
  }
}

// Usage
class MyButton extends StatelessComponent {
  const MyButton({super.key});

  @override
  Component build(BuildContext context) {
    final theme = Theme.of(context);
    
    return button(
      styles: Styles(
        backgroundColor: Color.hex(theme.primaryColor),
      ),
      [text('Click me')],
    );
  }
}
Key characteristics:
  • Must have a child component
  • Descendants can access the inherited data using dependOnInheritedComponentOfExactType<T>()
  • Descendants automatically rebuild when updateShouldNotify() returns true
  • Provides a way to share data without passing it through every component
When to use:
  • Sharing configuration data (themes, localization)
  • Providing services to descendants (authentication, routing)
  • Managing global or cross-cutting state
  • Avoiding prop drilling

Component Keys

Keys control how components are matched when rebuilding. They’re useful for preserving state when components move in the tree.
// Without a key, the framework might reuse the wrong element
List<Component> items = [
  Counter(key: ValueKey('counter-1')),
  Counter(key: ValueKey('counter-2')),
];

Key Types

  • ValueKey - Based on a specific value (string, number, etc.)
  • GlobalKey - Unique across the entire app, allows accessing state from anywhere
You typically only need keys when you have multiple children of the same type that can be reordered.

Performance Tips

Mark components and their constructors as const whenever possible. This allows Jaspr to skip rebuilding identical components.
const MyComponent({super.key, required this.title});
Keep your component trees shallow. Extract reusable pieces into separate components rather than building deeply nested trees.
Store components that don’t change in final variables rather than recreating them on every build.
class _MyState extends State<MyComponent> {
  final header = const Header();
  
  @override
  Component build(BuildContext context) {
    return div([header, ...]);
  }
}
Override shouldRebuild() to skip unnecessary rebuilds when component properties haven’t meaningfully changed.
@override
bool shouldRebuild(covariant MyComponent newComponent) {
  return title != newComponent.title;
}

HTML Elements

Learn how to render HTML with DomComponent

Styling

Style your components with the Styles class

Data Fetching

Load and manage asynchronous data

Routing

Navigate between pages in your app

Build docs developers (and LLMs) love