djust docs
Browse documentation

Components API Reference

On this page

class Component

Base class for stateless components — no events, no lifecycle, just rendering.

from djust.components.base import Component

Methods

__init__(**kwargs)

Pass initial values as keyword arguments. Call super().__init__(**kwargs).

get_context_data() -> dict

Return template context. Override to provide data to the template.

render() -> str

Renders the component to an HTML string. Called automatically by __str__().

Usage:

class Badge(Component):
    template = '<span class="badge badge-{{ variant }}">{{ label }}</span>'

    def __init__(self, label: str, variant: str = "primary"):
        super().__init__(label=label, variant=variant)
        self.label = label
        self.variant = variant

    def get_context_data(self):
        return {"label": self.label, "variant": self.variant}

In a LiveView:

def mount(self, request, **kwargs):
    self.status_badge = Badge("Active", variant="success")

def get_context_data(self, **kwargs):
    return {"status_badge": self.status_badge}

In the template: {{ status_badge }} (calls render() via __str__()). Add |safe ({{ status_badge|safe }}) until #2501's escaping fix lands — without it the markup renders as literal text.


class LiveComponent

Stateful component with its own mount/render lifecycle and event handlers.

from djust.components.base import LiveComponent

Class Attributes

AttributeTypeDescription
templatestrInline HTML template
template_namestrPath to a template file

Abstract Methods (must implement)

mount(**kwargs)

Called once when the component is first rendered. Initialize all state here.

def mount(self, **kwargs):
    self.count = kwargs.get("initial", 0)

get_context_data() -> dict

Return template context. Called before every render.

def get_context_data(self):
    return {"count": self.count}

Instance Methods

trigger_update()

Re-render this component and push the diff to the client. Call after changing state in an event handler.

send_parent(event, data=None)

Send an event to the parent LiveView.

Parameters:

  • event (str) — Event name string
  • data (dict, optional) — Event payload

The parent receives it via handle_component_event().

Critical Template Rule

Put data-component-id="{{ component_id }}" on the component's root element. The client walks up from the element that fired the event to the nearest data-component-id, so events from any descendant are routed to the component. Without it, events route to the parent LiveView:

<div data-component-id="{{ component_id }}">
    <button dj-click="increment">+</button>
    <span>{{ count }}</span>
</div>

Parent–Child Communication

# Component sends event up:
class CounterWidget(LiveComponent):
    @event_handler()
    def increment(self, **kwargs):
        self.count += 1
        self.trigger_update()
        self.send_parent("count_changed", {"count": self.count})

# Parent receives it:
class DashboardView(LiveView):
    def handle_component_event(self, component_id, event, data):
        if event == "count_changed":
            self.total = data["count"]

Built-in Components

AlertComponent

from djust.components import AlertComponent

self.alert = AlertComponent(
    message="Saved!",
    type="success",       # "success", "danger", "warning", "info"
    dismissible=True,
)

# Methods:
self.alert.show("New message!", "danger")
self.alert.dismiss()

ModalComponent

from djust.components import ModalComponent

self.modal = ModalComponent(
    title="Confirm Delete",
    body="This cannot be undone.",
    show=False,
)

# Methods:
self.modal.show()
self.modal.hide()

TabsComponent

from djust.components import TabsComponent

self.tabs = TabsComponent(
    tabs=[
        {"id": "tab1", "label": "Overview", "content": "<p>...</p>"},
        {"id": "tab2", "label": "Settings", "content": "<p>...</p>", "badge": "3"},
    ],
    active="tab1",
)

# Methods:
self.tabs.activate_tab("tab2")

TableComponent

from djust.components import TableComponent

self.table = TableComponent(
    columns=[
        {"key": "name", "label": "Name", "sortable": True},
        {"key": "email", "label": "Email"},
    ],
    rows=[
        {"name": "Alice", "email": "alice@example.com"},
    ],
    striped=True,
    hoverable=True,
)

A sortable column header carries aria-sort (none / ascending / descending) and a visual mark in the framework's icon convention — Bootstrap Icons classes on bootstrap5 (bi-arrow-down-up, bi-caret-up-fill, bi-caret-down-fill; load the Bootstrap Icons stylesheet), the vendored heroicons SVG on tailwind, and / / on plain.

selectable=True adds a checkbox per row and one in the header. A row is identified by its row_key value (default "id"), stored as a string in selected_rows — the same convention as {% data_table %}:

self.table = TableComponent(columns=..., rows=..., selectable=True, row_key="id")
# after the user ticks two rows:
self.table.selected_rows  # ["3", "7"]

The row checkbox toggles that row (toggle_row); the header checkbox selects every visible row, or clears the selection when every visible row is already selected (toggle_all).

filterable=True adds a global filter input above the table: rows stay visible when any column's string value contains the query, case-insensitively. {"filterable": True} on a column adds a filter input under that header that narrows on that column alone. Both compose (every filter must match), and they compose with the sort: rows are filtered, then sorted. The rows you passed are never narrowed — clearing an input restores them. The conventions mirror {% data_table %} / DataTableMixin (icontains, an empty value removes the filter, select-all is the post-filter set):

self.table = TableComponent(
    columns=[
        {"key": "name", "label": "Name", "sortable": True, "filterable": True},
        {"key": "email", "label": "Email"},
    ],
    rows=...,
    filterable=True,
)
# after the user types:
self.table.filter_query     # "ali"      — the global input (filter_rows)
self.table.column_filters   # {"name": "al"}  — per column (filter_column)

The inputs are dj-input controls debounced at 300 ms (dj-debounce="300"), routed to the component with data-component-id, and labelled (aria-label="Search table" / aria-label="Filter <label>").

Table anatomy and CSS classes

Use additive class hooks to customize the table without replacing its renderer. For Bootstrap, table_class="align-middle caption-top" enables vertical alignment and a top caption; thead_class="table-dark" styles the header. Supply utility classes appropriate to your stylesheet when using Tailwind or plain rendering.

self.table = TableComponent(
    columns=[{"key": "name", "label": "Name"}, {"key": "count", "label": "Count"}],
    rows=[{"name": "Alice", "count": 3}],
    table_class="align-middle caption-top",
    thead_class="table-dark",
    caption="Items by owner",
    caption_class="text-muted",
    footer={"name": "Total", "count": 3},
    tfoot_class="table-light",
)
OptionPurposeDefault
table_classClasses appended to the framework's table classes""
thead_class, tbody_class, tfoot_classClasses on the respective table sections""
captionText in a semantic <caption> before the headerNone (omitted)
caption_classClasses on the caption""
footerOne summary row, a mapping keyed like the data rowsNone (omitted)

Caption and footer text and all class strings are HTML-escaped. Footer cells follow the declared column order; missing keys render empty cells, and selection adds an empty alignment cell. The footer is a supplied summary, not an automatically calculated total, and is not sorted, filtered, or selected with the data rows.

PaginationComponent

from djust.components import PaginationComponent

self.pagination = PaginationComponent(
    current_page=1,
    total_pages=10,
    on_page_change="go_to_page",   # event handler name
)

ProgressComponent

from djust.components import ProgressComponent

self.progress = ProgressComponent(value=65, max_value=100, custom_label="65%")

ForeignKeySelect / ManyToManySelect

from djust.components.forms import ForeignKeySelect, ManyToManySelect

self.author_select = ForeignKeySelect(
    name="author",
    queryset=Author.objects.all(),
    label_field="name",
    searchable=True,
    search_fields=["name", "email"],
)

Component Registry

from djust.components import register_component, get_component, list_components

# Register a custom component globally
register_component("my_widget", MyWidgetComponent)

# Retrieve by name
cls = get_component("my_widget")

# List all registered components
registry = list_components()  # {'alert': AlertComponent, 'modal': ModalComponent, ...}

Stateless UI Components

Quick-use display components. No event handling.

from djust.components.ui import Badge, Button, Card, Alert

badge = Badge(text="New", variant="success")
button = Button(text="Click me", variant="primary", disabled=False)
card = Card(body="Content here", header="My Card")

Full list: Badge, Button, Card, Alert, Modal, Accordion, Avatar, Breadcrumb, Checkbox, Divider, Dropdown, Icon, Input, ListGroup, NavBar, Pagination, Progress, Radio, Range, Select, Spinner, Switch, Table, Tabs, TextArea, Toast, Tooltip


See Also

Markdown editor

See the Markdown Editor guide for optional Visual/Markdown editing, native form integration, asset loading, theme variables and editing limitations.