Modern, self-contained Jinja2 component library with built-in assets, designed for seamless integration with FastAPI, Flask, and other Python web frameworks.
- Custom Component Syntax: Use intuitive
<c-button>tags in your templates - Self-Contained Assets: All CSS, JavaScript, fonts, and icons bundled in the package
- RVO Design System: Built on the Netherlands Government Design System
- HTMX Integration: Optional HTMX support for dynamic interactions
- TypeScript Support: Full TypeScript development environment
- Framework Agnostic: Works with FastAPI, Flask, Django, and more
- Extensible: Easy to add new components
Most of this readme is generated by AI. Just check the examples folder for a working FastAPI example, for components reference, see the Component Reference when starting:
poetry run uvicorn examples.fastapi_app:app --reloadpip install jinja-roos-componentsFor development with asset building:
cd jinja-roos-components
npm install
npm run buildYou can find demo implementations in these repos:
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from jinja_roos_components import setup_components
app = FastAPI()
templates = Jinja2Templates(directory="templates")
# Setup ROOS components with optional features
setup_components(
templates,
theme="operations", # Theme: default, operations, dark
htmx=True, # Enable HTMX integration
user_css_files=[ # Additional CSS files
"/static/custom.css"
],
user_js_files=[ # Additional JS files
"/static/custom.js"
]
)
@app.get("/")
def home(request: Request):
return templates.TemplateResponse("index.html", {"request": request})from flask import Flask, render_template
from jinja2 import Environment, FileSystemLoader
from jinja_roos_components import setup_components
app = Flask(__name__)
# Setup Jinja2 with ROOS components
env = Environment(loader=FileSystemLoader('templates'))
setup_components(env, htmx=True)
@app.route("/")
def home():
return env.get_template("index.html").render()Create complete pages with the c-page component:
<!-- templates/index.html -->
<c-page title="My Application"
description="A modern web application"
bodyClass="app-layout">
<c-card title="Welcome">
<c-button kind="primary" label="Click me!" />
</c-card>
</c-page>For more complex layouts, extend base templates:
<!-- templates/base.html -->
{% extends "layouts/base.html.j2" %}
{% block title %}My Application{% endblock %}
{% block content %}
<c-card title="Welcome">
<c-button kind="primary" label="Click me!" />
</c-card>
{% endblock %}The library includes a comprehensive set of components including buttons, cards, forms, layouts, and more. For a complete list of available components and their usage:
- Live Examples: See the examples folder and run the FastAPI app
- Component Reference: Auto-generated documentation with all attributes and examples
To generate the latest component documentation:
poetry run python scripts/generate_component_docs.pyThis creates examples/component-reference.html with complete documentation for all components, including attributes, types, defaults, and usage examples.
- Create Component Template
Create a new Jinja2 template in src/jinja_roos_components/templates/components/:
<!-- jinja_roos_components/templates/components/input.html.j2 -->
{% set input_type = _component_context.type | default('text') %}
{% set placeholder = _component_context.placeholder | default('') %}
{% set required = _component_context.required | default(false) %}
{% set value = _component_context.value | default('') %}
{% set name = _component_context.name | default('') %}
<div class="rvo-form-field">
{% if _component_context.label %}
<label class="rvo-form-field__label" for="{{ name }}">
{{ _component_context.label }}
{% if required %}<span class="rvo-form-field__required">*</span>{% endif %}
</label>
{% endif %}
<input
class="rvo-form-field__input {{ _component_context.class | default('') }}"
type="{{ input_type }}"
id="{{ name }}"
name="{{ name }}"
value="{{ value }}"
placeholder="{{ placeholder }}"
{% if required %}required{% endif %}
data-roos-component="input"
{% if _component_context.get('@input') %}
oninput="{{ _component_context['@input'] }}"
{% endif %}
/>
</div>- Register Component Definition
Add to src/jinja_roos_components/definitions.json under components key:
{
"name": "input",
"description": "RVO text input with various types and validation",
"attributes": [
{
"name": "id",
"type": "string",
"required": false,
"description": "HTML id attribute"
},
{
"name": "name",
"type": "string",
"required": false,
"description": "HTML name attribute"
},
{
"name": "type",
"type": "enum",
"enum_values": [
"text",
"email",
"password",
"tel",
"url",
"search",
"number"
],
"default": "text",
"required": false,
"description": "Input type"
},
{
"name": "placeholder",
"type": "string",
"required": false,
"description": "Placeholder text"
},
{
"name": "disabled",
"type": "boolean",
"default": false,
"required": false,
"description": "Whether input is disabled"
},
{
"name": "required",
"type": "boolean",
"default": false,
"required": false,
"description": "Whether input is required"
},
{
"name": "size",
"type": "enum",
"enum_values": [
"xs",
"sm",
"md",
"lg",
"max"
],
"default": "md",
"required": false,
"description": "Input size"
}
],
"slots": [],
"examples": [
"<c-input name=\"email\" type=\"email\" placeholder=\"Enter email\" />"
],
"allow_preview": true,
"requires_children": false,
"preview_example": null
}- Create TypeScript Component (Optional)
Create src/ts/components/input.ts:
import type { Component } from '../utils/registry';
export const InputComponent: Component = {
selector: '[data-roos-component="input"]',
init(element: Element): void {
const input = element as HTMLInputElement;
// Add real-time validation
input.addEventListener('blur', () => {
this.validateInput(input);
});
// Add styling enhancements
input.addEventListener('focus', () => {
input.classList.add('rvo-form-field__input--focused');
});
input.addEventListener('blur', () => {
input.classList.remove('rvo-form-field__input--focused');
});
},
validateInput(input: HTMLInputElement): void {
// Custom validation logic
const isValid = input.checkValidity();
if (isValid) {
input.classList.remove('rvo-form-field__input--error');
input.classList.add('rvo-form-field__input--valid');
} else {
input.classList.add('rvo-form-field__input--error');
input.classList.remove('rvo-form-field__input--valid');
}
},
destroy(element: Element): void {
// Cleanup if needed
}
};- Register TypeScript Component
Add to src/ts/roos.ts:
import { InputComponent } from './components/input';
// Register the component
registry.register('input', InputComponent);- Build and Test
npm run buildTest your new component:
<c-input label="Email Address" type="email" required
placeholder="Enter your email" />- Start Development Server
npm run devThis watches for TypeScript changes and rebuilds automatically.
- Build for Production
npm run build- Code Quality
npm run lint # ESLint
npm run prettier # Code formatting
npm run typecheck # TypeScript checkingCreate test files in your project:
# test_components.py
from jinja2 import Environment, DictLoader
from jinja_roos_components import setup_components
def test_button_component():
env = Environment(loader=DictLoader({
'test.html': '<c-button variant="primary">Test</c-button>'
}))
setup_components(env)
template = env.get_template('test.html')
result = template.render()
assert 'rvo-button--primary' in result
assert 'Test' in result# In your operations-manager
from jinja_roos_components import setup_components
setup_components(
templates,
theme="operations",
htmx=True,
user_css_files=['/static/operations.css']
)<!-- Environment creation form -->
<c-card title="Create New Environment">
<form hx-post="/environments" hx-target="#result">
<c-input label="Project Name" name="project_name" required />
<c-input label="Environment" name="env_type" type="select"
:options="['dev', 'staging', 'prod']" />
<div class="form-actions">
<c-button type="submit" variant="primary"
@click="hx-indicator='#loading'">
Create Environment
</c-button>
<c-button variant="secondary" @click="history.back()">
Cancel
</c-button>
</div>
</form>
<div id="loading" class="htmx-indicator">
<c-button :loading="true">Creating...</c-button>
</div>
</c-card>Components not rendering:
- Ensure
setup_components()is called on your Jinja2 environment - Check that component templates are in the correct path
- Verify webpack build completed successfully
Styles not loading:
- Run
npm run buildto generate CSS assets - Check that static files are being served correctly
- Verify RVO design system dependencies are installed
TypeScript errors:
- Run
npm run typecheckto see detailed errors - Ensure all component files are properly imported
- Check TypeScript configuration in
tsconfig.json
HTMX integration issues:
- Ensure HTMX is loaded before ROOS components
- Check that
htmx=Trueis passed tosetup_components() - Verify HTMX attributes are properly formatted
- Lazy Loading: Components only initialize when found in DOM
- Asset Optimization: Production build includes minification and tree-shaking
- Component Reuse: Template includes are cached by Jinja2
- TypeScript Benefits: Type checking prevents runtime errors
Configure ROOS Components in a Jinja2 environment.
Parameters:
jinja_env: Environment- Jinja2 environment instancetheme: str- Theme name (default,operations,dark)htmx: bool- Enable HTMX integrationuser_css_files: list[str]- Additional CSS filesuser_js_files: list[str]- Additional JavaScript filesstatic_url_prefix: str- URL prefix for static assets
- String attributes:
attr="value" - Dynamic attributes:
:attr="expression" - Event handlers:
@event="handler" - HTMX attributes:
@click="hx-get='/api/data'"(when HTMX enabled)
- Fork the repository
- Create a feature branch:
git checkout -b feature/new-component - Add your component following the development guide
- Write tests for your component
- Run code quality checks:
npm run lint && npm run typecheck - Submit a pull request
This package bundles static assets built against specific versions of the RVO Design System npm packages:
| Package | Version |
|---|---|
@nl-rvo/assets |
1.0.0 |
@nl-rvo/component-library-css |
4.19.0 |
@nl-rvo/css-button |
2.1.0 |
@nl-rvo/design-tokens |
2.2.0 |
If your project loads any of these packages separately (e.g. via CDN in a base HTML template), use the same versions to avoid styling inconsistencies.
Note:
@nl-rvo/assets(fonts and icons) is always served from this package's bundled static files β no CDN reference needed.
Currently the package is not released on a package index. However, by using tags, users can still pin to a specific version.
- Change "[Unreleased]" in CHANGES.md to new tag and create new "[Unreleased]" section on top
- Change version in pyproject.toml
- Tag on main with
git tag -a 0.1 -m "0.1"(substitute new tags)
EUROPEAN UNION PUBLIC LICENSE v1.2 - see LICENSE file for details.