Introduction

djust is a powerful framework that brings Phoenix LiveView-style real-time interactivity to Django, powered by Rust's blazing-fast VDOM diffing.

⚡ Real-time Reactive

Server-rendered UI that updates in real-time via WebSockets. No JavaScript frameworks required.

🦀 Rust-Powered

Leverages Rust's performance for efficient VDOM diffing and minimal data transfer.

🎨 Framework Agnostic

Use with Bootstrap 5, Tailwind CSS, or plain HTML. Your choice.

📦 Component Library

Rich set of built-in components: buttons, alerts, forms, tables, modals, and more.

🔄 Django Forms

Seamless integration with Django's form system for validation and processing.

🎯 Zero JavaScript

Write all your logic in Python. No need to learn a frontend framework.

Why djust?
Build interactive, real-time web applications with the simplicity of server-side rendering and the responsiveness of modern SPAs. Perfect for dashboards, admin interfaces, forms, and real-time data displays.

Why djust?

The Django Developer's Dilemma

When building modern reactive UIs, Django developers traditionally face two difficult choices:

Option A: Add JavaScript Framework
  • Maintain two codebases (Python + JavaScript)
  • Complex build tooling (webpack, npm, etc.)
  • API layer complexity (REST/GraphQL)
  • State synchronization headaches
  • Slower time to market
Option B: Switch to Phoenix LiveView
  • Abandon Django ecosystem entirely
  • Lose Django Admin, ORM, middleware
  • Team must learn Elixir/functional programming
  • 6-12 month migration project
  • High risk, high cost
The Solution: djust
  • ✅ Keep Django ecosystem (Admin, ORM, Auth, 4,000+ packages)
  • ✅ Write reactive UIs in pure Python (no JavaScript required)
  • ✅ Rust-powered performance (10-100x faster rendering)
  • ✅ Incremental adoption (add to existing Django apps)
  • ✅ Ship features faster with less code

Blazing Fast Performance

Rust powers the performance-critical rendering path, delivering unprecedented speed:

Operation Django djust Speedup
Template Rendering (100 items) 2.5 ms 0.15 ms 16.7x faster
Large List (10k items) 450 ms 12 ms 37.5x faster
Virtual DOM Diff N/A 0.08 ms Sub-millisecond
Round-trip Update 50 ms 5 ms 10x faster
What This Means: Sub-millisecond rendering enables real-time experiences previously impossible in Django. Users get instant feedback on form validation, search, and filters. Your application feels native and responsive.

Keep Your Django Investment

Unlike Phoenix LiveView or JavaScript frameworks, you keep everything Django offers:

🔧 Django Admin

No equivalent in other frameworks

💾 Django ORM

Rich, mature, battle-tested

🔐 Django Auth

Complete authentication system

📦 4,000+ Packages

DRF, Celery, Allauth, and more

Write Less Code, Ship Faster

Stay in Python - no context switching between languages:

djust (Pure Python)
class TodoListView(LiveView):
    template_name = 'todos.html'

    def mount(self, request):
        self.todos = []

    def add_todo(self, text=""):
        self.todos.append({
            'text': text,
            'done': False
        })

    def toggle_todo(self, index=0):
        self.todos[index]['done'] = \\
            not self.todos[index]['done']

✅ One language, 50-70% less code

React + Django REST (Two Languages)
# Django backend
class TodoViewSet(viewsets.ModelViewSet):
    queryset = Todo.objects.all()
    serializer_class = TodoSerializer
// React frontend
function TodoList() {
  const [todos, setTodos] = useState([]);

  const addTodo = async (text) => {
    const res = await fetch('/api/todos/', {
      method: 'POST',
      body: JSON.stringify({ text })
    });
    const todo = await res.json();
    setTodos([...todos, todo]);
  };
  // More boilerplate...
}

❌ Two codebases, complex state sync

No Build Step Required

# djust - Simple!
pip install djust
# Add to INSTALLED_APPS
# Write code, refresh browser - done! ✅

# vs React/Vue/Svelte - Complex
npm install react webpack babel ...
# Configure webpack.config.js, babel.config.js, tsconfig.json...
npm run build  # Wait 30-60 seconds
# Hope build doesn't break in production ❌

Client bundle: Just ~5KB JavaScript (vs 100-300KB+ for React apps)

Perfect Use Cases

📊 Dashboards & Analytics

Real-time metrics, live data updates, interactive filtering and search.

🛒 E-commerce

Real-time inventory, live shopping cart, instant search, dynamic filters.

📝 Form-Heavy Apps

Real-time validation, multi-step wizards, dynamic fields, instant feedback.

🤝 Collaborative Tools

Real-time updates, live notifications, shared state visualization.

🔧 Admin Interfaces

Interactive data management, live search and filtering, instant updates.

📈 Data Visualization

Live charts and graphs, interactive filters, real-time data feeds.

Incremental Adoption

Add reactivity to specific components without rewriting your entire app:

  1. Start with traditional Django views
  2. Add LiveView to specific interactive components (filters, search, forms)
  3. Gradually increase coverage as needed
  4. No "big bang" rewrite required
# Keep existing Django views
class ProductListView(ListView):
    model = Product
    template_name = 'products/list.html'

# Add live filtering to just the filters widget
class LiveProductFilters(LiveView):
    def mount(self, request):
        self.search = ""
        self.categories = Category.objects.all()

    def filter_products(self, value=""):
        self.search = value
        # Real-time filtering without page reload!

How It Works

The secret: Rust handles the performance-critical path

┌─────────────────────────────────────────────┐
│  Developer writes Python (high productivity)│
│  class MyView(LiveView):                    │
│      def increment(self):                   │
│          self.count += 1                    │
└──────────────────┬──────────────────────────┘
                   │ Python/Rust FFI (PyO3)
┌──────────────────▼──────────────────────────┐
│  Rust executes hot path (high performance)  │
│  - Template parsing & rendering             │
│  - VDOM construction & diffing              │
│  - HTML parsing                             │
│  - Binary serialization (MessagePack)       │
└─────────────────────────────────────────────┘
                   │ WebSocket (Binary)
┌──────────────────▼──────────────────────────┐
│  Browser receives minimal updates           │
│  - Only changed DOM nodes                   │
│  - ~5KB client JavaScript                   │
│  - Instant UI updates                       │
└─────────────────────────────────────────────┘
The Best of Both Worlds:
Python where it matters (business logic, developer experience) + Rust where it matters (rendering, diffing, performance) = 10-100x faster than pure Python, simpler than JavaScript frameworks, without abandoning Django.

Installation

1. Install the Package

pip install djust

2. Add to INSTALLED_APPS

# settings.py
INSTALLED_APPS = [
    # ... other apps
    'djust',
]

3. Configure WebSocket Routing

# asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from djust.routing import websocket_urlpatterns

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": AuthMiddlewareStack(
        URLRouter(websocket_urlpatterns)
    ),
})

4. Add Required Settings

# settings.py
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels.layers.InMemoryChannelLayer"
    }
}

# For production, use Redis:
# CHANNEL_LAYERS = {
#     "default": {
#         "BACKEND": "channels_redis.core.RedisChannelLayer",
#         "CONFIG": {
#             "hosts": [("127.0.0.1", 6379)],
#         },
#     },
# }

5. Run with ASGI Server

# Development
python manage.py runserver

# Production with Daphne
daphne -b 0.0.0.0 -p 8000 myproject.asgi:application

# Production with Uvicorn
uvicorn myproject.asgi:application --host 0.0.0.0 --port 8000

Quick Start

Create your first LiveView in 5 minutes:

1. Create a LiveView

# views.py
from djust import LiveView
from djust.components import ButtonComponent, AlertComponent

class CounterView(LiveView):
    template_name = "counter.html"

    def mount(self, request):
        """Initialize state when page loads"""
        self.count = 0
        self.message = ""

        # Create interactive button
        self.increment_btn = ButtonComponent(
            label="Increment",
            variant="primary",
            on_click="increment"
        )

    def increment(self):
        """Handle button click"""
        self.count += 1
        self.message = f"Count is {self.count}"

2. Create a Template

<!-- templates/counter.html -->
{% extends "base.html" %}

{% block content %}
<div data-liveview-root>
    <h1>Count: {{ count }}</h1>
    {{ increment_btn.render }}

    {% if message %}
        <p class="text-success">{{ message }}</p>
    {% endif %}
</div>
{% endblock %}

3. Add URL Route

# urls.py
from django.urls import path
from .views import CounterView

urlpatterns = [
    path('counter/', CounterView.as_view(), name='counter'),
]
That's it! Visit /counter/ and click the button. The count updates in real-time without page reload!

Core Concepts

Architecture Overview

djust uses a server-centric architecture:

Browser                          Server
--------                         --------
1. User visits /page/    →       Django renders initial HTML
2. Browser receives HTML  ←       + LiveView JavaScript
3. WebSocket connects     ↔       WebSocket handler
4. User clicks button     →       Event sent via WS
5. Server processes       ←       Python method executed
6. VDOM diff calculated   ←       Rust computes minimal changes
7. DOM updated            ←       Only changes sent via WS

Key Components

  • LiveView - Server-side class managing page state and events
  • Components - Reusable UI elements (buttons, alerts, forms, etc.)
  • Templates - Django templates with data-liveview-root
  • Events - User interactions (@click, @submit, @change)
  • State - Python attributes automatically synced to templates
  • VDOM - Rust-powered diffing for efficient updates

One LiveView Per Page

Each URL route has one LiveView class that manages the entire page. Within that LiveView, you can:

  • Use multiple components
  • Manage multiple state variables
  • Handle many different events
  • Create complex, interactive UIs

LiveView

Lifecycle Methods

class MyView(LiveView):
    template_name = "my_template.html"

    def mount(self, request):
        """Called once when page first loads
        Initialize state and components here"""
        self.counter = 0
        self.user = request.user
        self.items = []

    def get_context_data(self, **kwargs):
        """Called on every render
        Add computed values to template context"""
        context = super().get_context_data(**kwargs)
        context['item_count'] = len(self.items)
        context['user_name'] = self.user.get_full_name()
        return context

    def handle_connect(self, request):
        """Called when WebSocket connects
        Optional: customize connection handling"""
        pass

    def handle_disconnect(self):
        """Called when WebSocket disconnects
        Optional: cleanup resources"""
        pass

State Management

Any instance attribute becomes available in your template:

def mount(self, request):
    # These are all accessible in templates as {{ variable_name }}
    self.counter = 0
    self.message = "Hello"
    self.items = ["a", "b", "c"]
    self.user_data = {"name": "John", "age": 30}
    self.is_active = True

Event Handlers

Define methods to handle user interactions:

def increment(self):
    """Called when element with @click="increment" is clicked"""
    self.counter += 1

def handle_submit(self):
    """Called when form with @submit="handle_submit" is submitted"""
    # Access form data via self.form_data
    query = self.form_data.get('query', '')
    self.results = search(query)

def delete_item(self, item_id):
    """Called with data-item-id parameter"""
    self.items = [i for i in self.items if i.id != int(item_id)]

Built-in Components

Button Component

from djust.components import ButtonComponent

self.my_button = ButtonComponent(
    label="Click Me",
    variant="primary",    # primary, secondary, success, danger, warning, info
    size="md",           # sm, md, lg
    on_click="handle_click",
    icon="plus",         # Bootstrap icon name
    disabled=False
)

Alert Component

from djust.components import AlertComponent

self.alert = AlertComponent(
    message="Operation successful!",
    variant="success",    # success, info, warning, danger
    dismissible=True,
    icon="check-circle"
)

Card Component

from djust.components import CardComponent

self.card = CardComponent(
    title="User Profile",
    content="

Profile content here

", footer="Last updated: Today", variant="light", # light, dark, primary, etc. image_url="/static/img/profile.jpg" )

Table Component

from djust.components import TableComponent

self.table = TableComponent(
    columns=[
        {"key": "name", "label": "Name", "sortable": True},
        {"key": "email", "label": "Email"},
        {"key": "created", "label": "Created", "sortable": True},
    ],
    data=users,
    striped=True,
    hover=True,
    on_row_click="view_user"
)

Modal Component

from djust.components import ModalComponent

self.modal = ModalComponent(
    title="Confirm Delete",
    content="Are you sure you want to delete this item?",
    size="md",           # sm, md, lg, xl
    show=False,          # Control visibility
    on_confirm="confirm_delete",
    on_cancel="close_modal"
)

See the Component Kitchen Sink for examples of all available components.

Automatic Component ID Management

Components automatically receive stable component_id values based on their attribute names. This eliminates manual ID management:

# When you write:
self.alert_success = AlertComponent(message="Success!")

# The framework automatically:
# 1. Sets component.component_id = "alert_success"
# 2. Persists this ID across renders and events
# 3. Uses it in HTML: data-component-id="alert_success"
# 4. Routes events back to the correct component
Why it works:
  • The attribute name (alert_success) is already unique within your view
  • It's stable across re-renders and WebSocket reconnections
  • Event handlers can reference components by their attribute names
  • No manual ID strings to keep in sync

Event Routing with Automatic IDs

Event handlers receive the component_id automatically and can use it to route to the correct component:

class MyView(LiveView):
    def mount(self, request):
        self.alert_warning = AlertComponent(
            message="Warning message",
            dismissible=True
        )
        # component_id is automatically "alert_warning"

    def dismiss(self, component_id: str = None):
        """Handle dismissal - automatically routes to correct component"""
        if component_id and hasattr(self, component_id):
            component = getattr(self, component_id)
            if hasattr(component, 'dismiss'):
                component.dismiss()  # component_id="alert_warning"

When the dismiss button is clicked, the client automatically sends component_id="alert_warning", and the handler uses getattr(self, "alert_warning") to find the component.

Event Handling

Event Directives

Attach event handlers using special @ attributes:

Directive Description Example
@click Handle click events <button @click="increment">
@submit Handle form submissions <form @submit="handle_submit">
@change Handle input changes <input @change="validate_field">
@input Handle input while typing <input @input="live_search">

Event Parameters

Pass data to event handlers using data-* attributes:

<!-- Template -->
<button
    @click="delete_item"
    data-item-id="{{ item.id }}"
    data-confirm="true"
>
    Delete
</button>
# View
def delete_item(self, item_id, confirm=None):
    """Receives parameters from data-* attributes"""
    if confirm == "true":
        self.items = [i for i in self.items if i.id != int(item_id)]

Form Data Access

Access form field values in submit handlers:

def handle_submit(self):
    # Form data automatically available in self.form_data
    username = self.form_data.get('username', '')
    password = self.form_data.get('password', '')
    remember = self.form_data.get('remember_me', False)

    # Process the data
    if authenticate(username, password):
        self.success_message = "Login successful!"

Django Forms Integration

djust seamlessly integrates with Django's form system for validation and processing.

Basic Form Integration

# forms.py
from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(max_length=100)
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea)

# views.py
from djust import LiveView
from .forms import ContactForm

class ContactFormView(LiveView):
    template_name = "contact.html"

    def mount(self, request):
        self.form = ContactForm()
        self.success_message = ""

    def handle_submit(self):
        # Create form instance with submitted data
        self.form = ContactForm(self.form_data)

        if self.form.is_valid():
            # Process the data
            name = self.form.cleaned_data['name']
            email = self.form.cleaned_data['email']
            message = self.form.cleaned_data['message']

            # Send email, save to DB, etc.
            send_mail(name, email, message)

            self.success_message = "Thank you! We'll be in touch."
            self.form = ContactForm()  # Reset form
        # Form errors automatically displayed in template

Auto-Rendering Forms

Use the auto_form template tag for automatic form rendering:

<!-- templates/contact.html -->
{% load djust %}

<div data-liveview-root>
    <form @submit="handle_submit">
        {% auto_form form framework="bootstrap5" %}

        <button type="submit" class="btn btn-primary">Submit</button>
    </form>

    {% if success_message %}
        <div class="alert alert-success">{{ success_message }}</div>
    {% endif %}
</div>

Framework Options

The auto_form tag supports multiple CSS frameworks:

<!-- Bootstrap 5 (default) -->
{% auto_form form framework="bootstrap5" %}

<!-- Tailwind CSS -->
{% auto_form form framework="tailwind" %}

<!-- Plain HTML -->
{% auto_form form framework="plain" %}

Real-time Validation

Validate fields as users type:

class SignupFormView(LiveView):
    def mount(self, request):
        self.form = SignupForm()
        self.username_error = ""

    def validate_username(self):
        # Called when username field changes
        username = self.form_data.get('username', '')

        if len(username) < 3:
            self.username_error = "Username must be at least 3 characters"
        elif User.objects.filter(username=username).exists():
            self.username_error = "Username already taken"
        else:
            self.username_error = ""
<input
    type="text"
    name="username"
    @change="validate_username"
    class="form-control"
>
{% if username_error %}
    <div class="text-danger">{{ username_error }}</div>
{% endif %}

See the Forms Demo for complete examples of form integration.

Templates

LiveView Root Element

All LiveView templates must have a root element with data-liveview-root:

{% extends "base.html" %}

{% block content %}
<div data-liveview-root>
    <!-- Your LiveView content here -->
    <h1>{{ title }}</h1>
    <p>{{ message }}</p>
</div>
{% endblock %}

Accessing State Variables

All instance attributes from your LiveView are available in templates:

# views.py
def mount(self, request):
    self.counter = 0
    self.items = ["apple", "banana", "cherry"]
    self.user = request.user
    self.config = {"theme": "dark", "notifications": True}
<!-- templates/my_view.html -->
<p>Count: {{ counter }}</p>

<ul>
    {% for item in items %}
        <li>{{ item }}</li>
    {% endfor %}
</ul>

<p>Welcome, {{ user.username }}!</p>
<p>Theme: {{ config.theme }}</p>

Rendering Components

Components are rendered using the .render attribute:

# views.py
def mount(self, request):
    self.save_btn = ButtonComponent(
        label="Save",
        variant="success",
        on_click="save_data"
    )
    self.alert = AlertComponent(
        message="Changes saved!",
        variant="success"
    )
<!-- Render components -->
{{ save_btn.render }}
{{ alert.render }}

Conditional Rendering

<!-- Show/hide based on state -->
{% if is_loading %}
    <div class="spinner">Loading...</div>
{% else %}
    <div class="content">{{ data }}</div>
{% endif %}

<!-- Render list items -->
{% for item in items %}
    <div class="item">
        <h3>{{ item.title }}</h3>
        <button @click="delete" data-item-id="{{ item.id }}">Delete</button>
    </div>
{% empty %}
    <p>No items found.</p>
{% endfor %}

Template Tags

djust provides custom template tags:

{% load djust %}

<!-- Auto-render Django forms -->
{% auto_form form framework="bootstrap5" %}

<!-- Component rendering helpers -->
{% render_component component %}

State Management

Simple State

State is managed as instance attributes on your LiveView class:

class TodoView(LiveView):
    def mount(self, request):
        # Initialize state
        self.todos = []
        self.new_todo = ""
        self.filter = "all"  # all, active, completed

    def add_todo(self):
        # Modify state - UI updates automatically
        if self.new_todo.strip():
            self.todos.append({
                "id": len(self.todos) + 1,
                "text": self.new_todo,
                "completed": False
            })
            self.new_todo = ""  # Reset input

    def toggle_todo(self, todo_id):
        # Update nested state
        for todo in self.todos:
            if todo["id"] == int(todo_id):
                todo["completed"] = not todo["completed"]

    def delete_todo(self, todo_id):
        # Remove from state
        self.todos = [t for t in self.todos if t["id"] != int(todo_id)]

Computed Properties

Use get_context_data for computed values:

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)

    # Compute derived values
    context['active_count'] = sum(1 for t in self.todos if not t['completed'])
    context['completed_count'] = sum(1 for t in self.todos if t['completed'])
    context['filtered_todos'] = self._get_filtered_todos()

    return context

def _get_filtered_todos(self):
    if self.filter == "active":
        return [t for t in self.todos if not t['completed']]
    elif self.filter == "completed":
        return [t for t in self.todos if t['completed']]
    return self.todos

State Persistence

State can be persisted to database, session, or cache:

class ShoppingCartView(LiveView):
    def mount(self, request):
        # Load from session
        self.cart_items = request.session.get('cart', [])

    def add_to_cart(self, product_id):
        self.cart_items.append({"id": product_id, "quantity": 1})
        # Save to session
        self.request.session['cart'] = self.cart_items

    def handle_disconnect(self):
        # Save to database on disconnect
        if self.request.user.is_authenticated:
            Cart.objects.update_or_create(
                user=self.request.user,
                defaults={'items': self.cart_items}
            )

State Updates Trigger Re-renders

Any state change automatically triggers a re-render:

def increment(self):
    self.counter += 1  # UI updates automatically

def load_data(self):
    self.is_loading = True  # Shows loading spinner
    self.data = fetch_from_api()
    self.is_loading = False  # Hides spinner, shows data
Performance Tip: Only the changed parts of the DOM are updated thanks to Rust-powered VDOM diffing. Large state changes result in minimal network transfer.

Creating Custom Components

Component Class

Create reusable components by inheriting from LiveComponent:

# components.py
from djust.components import LiveComponent

class UserCardComponent(LiveComponent):
    """Reusable user profile card"""

    template_name = "components/user_card.html"

    def __init__(self, user, show_actions=True):
        super().__init__()
        self.user = user
        self.show_actions = show_actions
        self.is_following = False

    def toggle_follow(self):
        """Handle follow/unfollow action"""
        self.is_following = not self.is_following

        if self.is_following:
            Follow.objects.create(follower=self.current_user, following=self.user)
        else:
            Follow.objects.filter(follower=self.current_user, following=self.user).delete()

Component Template

<!-- templates/components/user_card.html -->
<div class="card">
    <img src="{{ user.avatar_url }}" class="card-img-top" alt="Avatar">
    <div class="card-body">
        <h5 class="card-title">{{ user.get_full_name }}</h5>
        <p class="card-text">{{ user.bio }}</p>

        {% if show_actions %}
            <button
                @click="toggle_follow"
                class="btn btn-{% if is_following %}secondary{% else %}primary{% endif %}"
            >
                {% if is_following %}Unfollow{% else %}Follow{% endif %}
            </button>
        {% endif %}
    </div>
</div>

Using Custom Components

# views.py
from .components import UserCardComponent

class ProfileView(LiveView):
    template_name = "profile.html"

    def mount(self, request):
        users = User.objects.all()[:5]

        # Create component instances
        self.user_cards = [
            UserCardComponent(user=user, show_actions=True)
            for user in users
        ]
<!-- templates/profile.html -->
<div data-liveview-root>
    <h1>Suggested Users</h1>
    <div class="row">
        {% for card in user_cards %}
            <div class="col-md-4">
                {{ card.render }}
            </div>
        {% endfor %}
    </div>
</div>

Component Lifecycle

class MyComponent(LiveComponent):
    def mount(self):
        """Called when component is created"""
        self.initialize_state()

    def update(self, **kwargs):
        """Called when component receives new props"""
        self.user = kwargs.get('user')
        self.refresh_data()

    def destroy(self):
        """Called when component is removed"""
        self.cleanup()
Note: Components maintain their own state and can handle their own events independently of the parent LiveView.

Styling

CSS Framework Support

djust works with any CSS framework:

Bootstrap 5

<!-- base.html -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">

<!-- Components use Bootstrap classes by default -->
{{ button.render }}  <!-- Renders with btn, btn-primary, etc. -->

Tailwind CSS

<!-- base.html -->
<script src="https://cdn.tailwindcss.com"></script>

<!-- Use Tailwind classes in templates -->
<div class="bg-blue-500 text-white p-4 rounded-lg">
    {{ content }}
</div>

Custom Styling

<!-- Add custom styles in your template -->
{% block extra_styles %}
    .my-custom-card {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        border-radius: 12px;
        padding: 2rem;
        color: white;
    }

    .pulse-animation {
        animation: pulse 2s infinite;
    }

    @keyframes pulse {
        0%, 100% { opacity: 1; }
        50% { opacity: 0.5; }
    }
{% endblock %}

Dynamic Classes

Apply classes conditionally based on state:

<button
    class="btn {% if is_active %}btn-primary{% else %}btn-secondary{% endif %}"
    @click="toggle"
>
    Toggle
</button>

<div class="item {% if item.completed %}completed{% endif %} {% if item.important %}important{% endif %}">
    {{ item.title }}
</div>

Component Styling

Customize built-in components:

self.button = ButtonComponent(
    label="Custom Button",
    variant="primary",
    css_class="my-custom-class shadow-lg",  # Additional classes
    style="min-width: 200px;"  # Inline styles
)

Deployment

Production Requirements

For production deployment, you need:

  • ASGI Server - Daphne, Uvicorn, or Hypercorn
  • Redis - For channel layer (multi-worker support)
  • WebSocket Support - Reverse proxy configured for WebSockets

Redis Configuration

# settings.py
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("redis", 6379)],
            "capacity": 1500,
            "expiry": 10,
        },
    },
}

Nginx Configuration

# nginx.conf
upstream django {
    server web:8000;
}

server {
    listen 80;
    server_name example.com;

    # WebSocket support
    location /ws/ {
        proxy_pass http://django;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 86400;
    }

    # Regular HTTP
    location / {
        proxy_pass http://django;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Static files
    location /static/ {
        alias /app/static/;
    }
}

Docker Compose

# docker-compose.yml
version: '3.8'

services:
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  web:
    build: .
    command: daphne -b 0.0.0.0 -p 8000 myproject.asgi:application
    volumes:
      - .:/app
    ports:
      - "8000:8000"
    depends_on:
      - redis
    environment:
      - DJANGO_SETTINGS_MODULE=myproject.settings.production
      - REDIS_HOST=redis

Environment Variables

# .env
DEBUG=False
SECRET_KEY=your-secret-key-here
ALLOWED_HOSTS=example.com,www.example.com
REDIS_HOST=redis
DATABASE_URL=postgres://user:pass@db:5432/dbname

Running with Daphne

# Install Daphne
pip install daphne

# Run production server
daphne -b 0.0.0.0 -p 8000 myproject.asgi:application

Running with Uvicorn

# Install Uvicorn
pip install uvicorn[standard]

# Run production server
uvicorn myproject.asgi:application --host 0.0.0.0 --port 8000 --workers 4
Important: Always use Redis for the channel layer in production. The in-memory backend doesn't support multiple workers.

API Reference

LiveView Class

Method/Attribute Description
template_name Path to the template file
mount(request) Initialize state when page first loads
get_context_data(**kwargs) Add computed values to template context
handle_connect(request) Called when WebSocket connects
handle_disconnect() Called when WebSocket disconnects
form_data Dictionary of form field values from events
request Django request object

LiveComponent Class

Method/Attribute Description
template_name Path to the component template file
mount() Called when component is created
update(**kwargs) Called when component receives new props
destroy() Called when component is removed
render Property that returns rendered HTML

Built-in Components

Component Key Parameters
ButtonComponent label, variant, size, on_click, icon, disabled
AlertComponent message, variant, dismissible, icon
CardComponent title, content, footer, variant, image_url
TableComponent columns, data, striped, hover, on_row_click
ModalComponent title, content, size, show, on_confirm, on_cancel
BadgeComponent text, variant, pill
ProgressComponent value, max, variant, striped, animated

Event Directives

Directive Trigger Example
@click Element clicked <button @click="increment">
@submit Form submitted <form @submit="handle_form">
@change Input value changed <select @change="filter">
@input Input while typing <input @input="search">

Template Tags

Tag Description Example
{% auto_form %} Auto-render Django forms {% auto_form form framework="bootstrap5" %}
{% render_component %} Render a component {% render_component card %}
Need Help?
Check out the Component Kitchen Sink for live examples, or visit the GitHub repository for more documentation.