Components
Components in Starlette-Templates are reusable UI elements built with Jinja2 templates and Pydantic models. They provide:
- Type safety with Pydantic validation
- Reusable, testable UI elements
- Template-based rendering
- Integration with the Jinja2 environment
Creating Components
Define components using the ComponentModel base class:
from starlette_templates.components.base import ComponentModel
from pydantic import Field
class Alert(ComponentModel):
# Path to component template
template: str = "components/alert.html"
# Component properties with validation
message: str = Field(..., description="Alert message")
variant: str = Field(default="info", description="Alert variant")
dismissible: bool = Field(default=False, description="Show close button")
Component Template
Create the template file (components/alert.html):
<div class="alert alert-{{ variant }}{% if dismissible %} alert-dismissible{% endif %}" role="alert">
{{ message }}
{% if dismissible %}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
{% endif %}
</div>
Using Components
In Routes
Create component instances and pass them to templates:
from starlette.requests import Request
from starlette_templates.responses import TemplateResponse
async def dashboard(request: Request) -> TemplateResponse:
alert = Alert(
message="Welcome back!",
variant="success",
dismissible=True
)
return TemplateResponse(
"dashboard.html",
context={"alert": alert}
)
In Templates
Render components by calling them:
{{ alert }}
Global Registration
Register components globally to use them in any template:
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette_templates.middleware import JinjaEnvMiddleware
app = Starlette(
middleware=[
Middleware(
JinjaEnvMiddleware,
template_loaders=[PackageLoader("myapp", "templates")],
extra_components=[Alert], # Register component
)
]
)
Then use directly in templates without passing from routes:
{{ Alert(message="System error", variant="danger", dismissible=True) }}
Built-in Form Components
Starlette Templates includes Bootstrap-compatible form components:
Input
Input - Text, email, password, number inputs:
from starlette_templates.components.forms import Input
# In route
input_field = Input(
name="username",
label="Username",
type="text",
placeholder="Enter username",
required=True,
)
# In template
{{ Input(name="email", label="Email", type="email", required=True) }}
Textarea
Textarea - Multi-line text input:
from starlette_templates.components.forms import Textarea
textarea = Textarea(
name="message",
label="Message",
rows=5,
placeholder="Enter your message",
)
Select
Select - Native select dropdown:
from starlette_templates.components.forms import Select
select = Select(
name="country",
label="Country",
choices={
"us": "United States",
"uk": "United Kingdom",
"ca": "Canada",
},
selected="us",
)
Checkbox
Checkbox - Checkbox input:
from starlette_templates.components.forms import Checkbox
checkbox = Checkbox(
name="agree",
label="I agree to the terms",
checked=False,
)
Radio
Radio - Radio button:
from starlette_templates.components.forms import Radio
# In template
{{ Radio(name="plan", label="Basic Plan", value="basic", checked=True) }}
{{ Radio(name="plan", label="Pro Plan", value="pro") }}
Switch
Switch - Toggle switch:
from starlette_templates.components.forms import Switch
switch = Switch(
name="notifications",
label="Enable notifications",
checked=True,
)
FileInput
FileInput - File upload:
from starlette_templates.components.forms import FileInput
file_input = FileInput(
name="avatar",
label="Profile Picture",
accept="image/*",
)
Range
Range - Range slider:
from starlette_templates.components.forms import Range
range_slider = Range(
name="volume",
label="Volume",
min_value=0,
max_value=100,
step=1,
value=50,
)
ChoicesSelect
ChoicesSelect - Enhanced select with Choices.js:
from starlette_templates.components.forms import ChoicesSelect
choices_select = ChoicesSelect(
name="tags",
label="Tags",
choices={
"python": "Python",
"javascript": "JavaScript",
"go": "Go",
},
multiple=True,
)
DatePicker
DatePicker - Date picker with Flatpickr:
from starlette_templates.components.forms import DatePicker
date_picker = DatePicker(
name="birth_date",
label="Birth Date",
placeholder="Select date",
)
SubmitButton
SubmitButton - Form submit button:
from starlette_templates.components.forms import SubmitButton
submit_btn = SubmitButton(
text="Submit Form",
variant="primary",
)
Component Examples
Card Component
class Card(ComponentModel):
template: str = "components/card.html"
title: str = Field(..., description="Card title")
body: str = Field(..., description="Card body content")
footer: str | None = Field(None, description="Card footer")
variant: str = Field(default="default", description="Card style variant")
Template (components/card.html):
<div class="card{% if variant != 'default' %} border-{{ variant }}{% endif %}">
<div class="card-header">
<h5 class="card-title">{{ title }}</h5>
</div>
<div class="card-body">
{{ body }}
</div>
{% if footer %}
<div class="card-footer">
{{ footer }}
</div>
{% endif %}
</div>
Usage:
{{ Card(
title="Welcome",
body="This is a card component",
footer="Card footer",
variant="primary"
) }}
Badge Component
class Badge(ComponentModel):
template: str = "components/badge.html"
text: str = Field(..., description="Badge text")
variant: str = Field(default="primary", description="Badge color")
pill: bool = Field(default=False, description="Use pill style")
Template (components/badge.html):
<span class="badge bg-{{ variant }}{% if pill %} rounded-pill{% endif %}">
{{ text }}
</span>
Usage:
{{ Badge(text="New", variant="success", pill=True) }}
{{ Badge(text="Beta", variant="warning") }}
Button Component
class Button(ComponentModel):
template: str = "components/button.html"
text: str = Field(..., description="Button text")
variant: str = Field(default="primary", description="Button style")
size: str = Field(default="md", description="Button size")
disabled: bool = Field(default=False, description="Disabled state")
type: str = Field(default="button", description="Button type")
Template (components/button.html):
<button
type="{{ type }}"
class="btn btn-{{ variant }} btn-{{ size }}"
{% if disabled %}disabled{% endif %}
>
{{ text }}
</button>
Usage:
{{ Button(text="Click Me", variant="primary") }}
{{ Button(text="Submit", variant="success", type="submit") }}
{{ Button(text="Disabled", disabled=True) }}
Modal Component
class Modal(ComponentModel):
template: str = "components/modal.html"
id: str = Field(..., description="Modal ID")
title: str = Field(..., description="Modal title")
body: str = Field(..., description="Modal body content")
footer: str | None = Field(None, description="Modal footer")
size: str = Field(default="md", description="Modal size (sm, md, lg, xl)")
Template (components/modal.html):
<div class="modal fade" id="{{ id }}" tabindex="-1">
<div class="modal-dialog modal-{{ size }}">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ title }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
{{ body }}
</div>
{% if footer %}
<div class="modal-footer">
{{ footer }}
</div>
{% endif %}
</div>
</div>
</div>
Component Validation
Components use Pydantic validation to ensure correct usage:
class Image(ComponentModel):
template: str = "components/image.html"
src: str = Field(..., description="Image source URL")
alt: str = Field(..., description="Alt text")
width: int | None = Field(None, ge=1, description="Image width")
height: int | None = Field(None, ge=1, description="Image height")
@field_validator('src')
@classmethod
def validate_src(cls, v):
if not v.startswith(('http://', 'https://', '/')):
raise ValueError('Invalid image source')
return v
Nested Components
Components can render other components:
class Alert(ComponentModel):
template: str = "components/alert.html"
message: str
variant: str = "info"
class AlertGroup(ComponentModel):
template: str = "components/alert_group.html"
alerts: list[Alert] = Field(default_factory=list)
Template (components/alert_group.html):
<div class="alert-group">
{% for alert in alerts %}
{{ alert }}
{% endfor %}
</div>
Usage:
alert_group = AlertGroup(
alerts=[
Alert(message="Success!", variant="success"),
Alert(message="Warning!", variant="warning"),
Alert(message="Error!", variant="danger"),
]
)