django-components/docs/concepts/fundamentals/rendering_components.md
Juro Oravec 59f82307ae
docs: docstrings, fundamentals, and minor changes (#1145)
* docs: docstrings, fundamentals, and minor changes

* refactor: fix tests + linter errors
2025-04-24 12:47:04 +02:00

21 KiB

Your components can be rendered either within your Django templates, or directly in Python code.

Overview

Django Components provides three main methods to render components:

{% component %} tag

Use the {% component %} tag to render a component within your Django templates.

The {% component %} tag takes:

  • Component's registered name as the first positional argument,
  • Followed by any number of positional and keyword arguments.
{% load component_tags %}
<div>
  {% component "button" name="John" job="Developer" / %}
</div>

To pass in slots content, you can insert {% fill %} tags, directly within the {% component %} tag to "fill" the slots:

{% component "my_table" rows=rows headers=headers %}
    {% fill "pagination" %}
      < 1 | 2 | 3 >
    {% endfill %}
{% endcomponent %}

You can even nest {% fill %} tags within {% if %}, {% for %} and other tags:

{% component "my_table" rows=rows headers=headers %}
    {% if rows %}
        {% fill "pagination" %}
            < 1 | 2 | 3 >
        {% endfill %}
    {% endif %}
{% endcomponent %}

!!! info "Omitting the component keyword"

If you would like to omit the `component` keyword, and simply refer to your
components by their registered names:

```django
{% button name="John" job="Developer" / %}
```

You can do so by setting the "shorthand" [Tag formatter](../../advanced/tag_formatters) in the settings:

```python
# settings.py
COMPONENTS = {
    "tag_formatter": "django_components.component_shorthand_formatter",
}
```

!!! info "Extended template tag syntax"

Unlike regular Django template tags, django-components' tags offer extra features like
defining literal lists and dicts, and more. Read more about [Template tag syntax](../template_tag_syntax).

Registering components

For a component to be renderable with the {% component %} tag, it must be first registered with the @register() decorator.

For example, if you register a component under the name "button":

from typing import NamedTuple
from django_components import Component, register

@register("button")
class Button(Component):
    template_file = "button.html"

    class Kwargs(NamedTuple):
        name: str
        job: str

    def get_template_data(self, args, kwargs, slots, context):
        ...

Then you can render this component by using its registered name "button" in the template:

{% component "button" name="John" job="Developer" / %}

As you can see above, the args and kwargs passed to the {% component %} tag correspond to the component's input.

For more details, read Registering components.

!!! note "Why do I need to register components?"

TL;DR: To be able to share components as libraries, and because components can be registed with multiple registries / libraries.

Django-components allows to [share components across projects](../../advanced/component_libraries).

However, different projects may use different settings. For example, one project may prefer the "long" format:

```django
{% component "button" name="John" job="Developer" / %}
```

While the other may use the "short" format:

```django
{% button name="John" job="Developer" / %}
```

Both approaches are supported simultaneously for backwards compatibility, because django-components
started out with only the "long" format.

To avoid ambiguity, when you use a 3rd party library, it uses the syntax that the author
had configured for it.

So when you are creating a component, django-components need to know which registry the component
belongs to, so it knows which syntax to use.

Rendering templates

If you have embedded the component in a Django template using the {% component %} tag:

{% load component_tags %}
<div>
  {% component "calendar" date="2024-12-13" / %}
</div>

You can simply render the template with the Django's API:

  • django.shortcuts.render()

    from django.shortcuts import render
    
    context = {"date": "2024-12-13"}
    rendered_template = render(request, "my_template.html", context)
    
  • Template.render()

    from django.template import Template
    from django.template.loader import get_template
    
    # Either from a file
    template = get_template("my_template.html")
    
    # or inlined
    template = Template("""
        {% load component_tags %}
        <div>
            {% component "calendar" date="2024-12-13" / %}
        </div>
    """)
    
    rendered_template = template.render()
    

Isolating components

By default, components behave similarly to Django's {% include %}, and the template inside the component has access to the variables defined in the outer template.

You can selectively isolate a component, using the only flag, so that the inner template can access only the data that was explicitly passed to it:

{% component "name" positional_arg keyword_arg=value ... only / %}

Alternatively, you can set all components to be isolated by default, by setting context_behavior to "isolated" in your settings:

# settings.py
COMPONENTS = {
    "context_behavior": "isolated",
}

render() method

The Component.render() method renders a component to a string.

This is the equivalent of calling the {% component %} tag.

from typing import NamedTuple, Optional
from django_components import Component, SlotInput

class Button(Component):
    template_file = "button.html"

    class Args(NamedTuple):
        name: str

    class Kwargs(NamedTuple):
        surname: str
        age: int

    class Slots(NamedTuple):
        footer: Optional[SlotInput] = None

    def get_template_data(self, args, kwargs, slots, context):
        ...

Button.render(
    args=["John"],
    kwargs={
        "surname": "Doe",
        "age": 30,
    },
    slots={
        "footer": "i AM A SLOT",
    },
)

Component.render() accepts the following arguments:

  • args - Positional arguments to pass to the component (as a list or tuple)
  • kwargs - Keyword arguments to pass to the component (as a dictionary)
  • slots - Slot content to pass to the component (as a dictionary)
  • context - Django context for rendering (can be a dictionary or a Context object)
  • type - Type of rendering (default: "document")
  • request - HTTP request object, used for context processors (optional)
  • escape_slots_content - Whether to HTML-escape slot content (default: True)
  • render_dependencies - Whether to process JS and CSS dependencies (default: True)

All arguments are optional. If not provided, they default to empty values or sensible defaults.

See the API reference for Component.render() for more details on the arguments.

render_to_response() method

The Component.render_to_response() method works just like Component.render(), but wraps the result in an HTTP response.

It accepts all the same arguments as Component.render().

Any extra arguments are passed to the HttpResponse constructor.

from typing import NamedTuple, Optional
from django_components import Component, SlotInput

class Button(Component):
    template_file = "button.html"

    class Args(NamedTuple):
        name: str

    class Kwargs(NamedTuple):
        surname: str
        age: int

    class Slots(NamedTuple):
        footer: Optional[SlotInput] = None

    def get_template_data(self, args, kwargs, slots, context):
        ...

# Render the component to an HttpResponse
response = Button.render_to_response(
    args=["John"],
    kwargs={
        "surname": "Doe",
        "age": 30,
    },
    slots={
        "footer": "i AM A SLOT",
    },
    # Additional response arguments
    status=200,
    headers={"X-Custom-Header": "Value"},
)

This method is particularly useful in view functions, as you can return the result of the component directly:

def profile_view(request, user_id):
    return Button.render_to_response(
        kwargs={
            "surname": "Doe",
            "age": 30,
        },
        request=request,
    )

Custom response classes

By default, Component.render_to_response() returns a standard Django HttpResponse.

You can customize this by setting the response_class attribute on your component:

from django.http import HttpResponse
from django_components import Component

class MyHttpResponse(HttpResponse):
    ...

class MyComponent(Component):
    response_class = MyHttpResponse

response = MyComponent.render_to_response()
assert isinstance(response, MyHttpResponse)

Render types

The rendered HTML may be used in different contexts (browser, email, etc). If your components use JS and CSS scripts, you need to handle them differently.

render() and render_to_response() accept a type parameter, which controls this behavior.

The type parameter is set at the root of a component render tree, which is why it is not available for the {% component %} tag.

!!! info

The `type` parameter is ultimately passed to [`render_dependencies()`](../../../reference/api/#django_components.render_dependencies).
Learn more about [Rendering JS / CSS](../../advanced/rendering_js_css).

There are three render types:

document

type="document" is the default. Use this if you are rendering a whole page, or if no other option suits better.

html = Button.render(type="document")

When you render a component tree with the "document" type, it is expected that:

  • The HTML will be rendered at page load.
  • The HTML will be inserted into a page / browser where JS can be executed.

With this setting, the JS and CSS is set up to avoid any delays for end users:

  • Components' primary JS and CSS scripts (Component.js and Component.css) are inlined into the rendered HTML.

    <script>
        console.log("Hello from Button!");
    </script>
    <style>
        .button {
        background-color: blue;
        }
    </style>
    
  • Components' secondary JS and CSS scripts (Component.Media) are inserted into the rendered HTML as links.

    <link rel="stylesheet" href="https://example.com/styles.css" />
    <script src="https://example.com/script.js"></script>
    
  • A JS script is injected to manage component dependencies, enabling lazy loading of JS and CSS for HTML fragments.

!!! info

This render type is required for fragments to work properly, as it sets up the dependency manager that fragments rely on.

!!! note "How the dependency manager works"

The dependency manager is a JS script that keeps track of all the JS and CSS dependencies that have already been loaded.

When a fragment is inserted into the page, it will also insert a JSON `<script>` tag with fragment metadata.

The dependency manager will pick up on that, and check which scripts the fragment needs.

It will then fetch only the scripts that haven't been loaded yet.

fragment

type="fragment" is used when rendering a piece of HTML that will be inserted into a page that has already been rendered with the "document" type:

fragment = MyComponent.render(type="fragment")

The HTML of fragments is very lightweight because it doesn't include the JS and CSS scripts of the rendered components.

With fragments, even if a component has JS and CSS, you can insert the same component into a page hundreds of times, and the JS and CSS will only ever be loaded once.

The fragment type:

  • Does not include the dependency manager script (assumes it's already loaded)
  • Does not inline JS or CSS directly in the HTML
  • Includes a special JSON <script> tag that tells the dependency manager what JS and CSS to load
  • The dependency manager will fetch only scripts that haven't been loaded yet

This is intended for dynamic content that's loaded after the initial page load, such as with HTMX or similar.

Passing context

The render() and render_to_response() methods accept an optional context argument. This sets the context within which the component is rendered.

When a component is rendered within a template with the {% component %} tag, this will be automatically set to the Context instance that is used for rendering the template.

When you call Component.render() directly from Python, there is no context object, so you can ignore this input most of the time. Instead, use args, kwargs, and slots to pass data to the component.

However, you can pass RequestContext to the context argument, so that the component will gain access to the request object and will use context processors. Read more on Working with HTTP requests.

Button.render(
    context=RequestContext(request),
)

For advanced use cases, you can use context argument to "pre-render" the component in Python, and then pass the rendered output as plain string to the template. With this, the inner component is rendered as if it was within the template with {% component %}.

class Button(Component):
    def render(self, context, template):
        # Pass `context` to Icon component so it is rendered
        # as if nested within Button.
        icon = Icon.render(
            context=context,
            args=["icon-name"],
            render_dependencies=False,
        )
        # Update context with icon
        with context.update({"icon": icon}):
            return template.render(context)

!!! warning

Whether the variables defined in `context` are actually available in the template depends on the
[context behavior mode](../../../reference/settings#django_components.app_settings.ComponentsSettings.context_behavior):

- In `"django"` context behavior mode, the template will have access to the keys of this context.

- In `"isolated"` context behavior mode, the template will NOT have access to this context,
    and data MUST be passed via component's args and kwargs.

Therefore, it's **strongly recommended** to not rely on defining variables on the context object,
but instead passing them through as `args` and `kwargs`

❌ Don't do this:

```python
html = ProfileCard.render(
    context={"name": "John"},
)
```

✅ Do this:

```python
html = ProfileCard.render(
    kwargs={"name": "John"},
)
```

Typing render methods

Neither Component.render() nor Component.render_to_response() are typed, due to limitations of Python's type system.

To add type hints, you can wrap the inputs in component's Args, Kwargs, and Slots classes.

Read more on Typing and validation.

from typing import NamedTuple, Optional
from django_components import Component, Slot, SlotInput

# Define the component with the types
class Button(Component):
    class Args(NamedTuple):
        name: str

    class Kwargs(NamedTuple):
        surname: str
        age: int

    class Slots(NamedTuple):
        my_slot: Optional[SlotInput] = None
        footer: SlotInput

# Add type hints to the render call
Button.render(
    args=Button.Args(
        name="John",
    ),
    kwargs=Button.Kwargs(
        surname="Doe",
        age=30,
    ),
    slots=Button.Slots(
        footer=Slot(lambda *a, **kwa: "Click me!"),
    ),
)

Nesting components

You can nest components by rendering one component and using its output as input to another:

from django.utils.safestring import mark_safe

# Render the inner component
inner_html = InnerComponent.render(
    kwargs={"some_data": "value"},
    render_dependencies=False,  # Important for nesting!
)

# Use inner component's output in the outer component
outer_html = OuterComponent.render(
    kwargs={
        "content": mark_safe(inner_html),  # Mark as safe to prevent escaping
    },
)

The key here is setting render_dependencies=False for the inner component. This prevents duplicate dependencies when the outer component is rendered.

When render_dependencies=False:

  • No JS or CSS dependencies will be added to the output HTML
  • The component's content is rendered as-is
  • The outer component will take care of including all needed dependencies

Read more about Rendering JS / CSS.

Dynamic components

Django components defines a special "dynamic" component (DynamicComponent).

Normally, you have to hard-code the component name in the template:

{% component "button" / %}

The dynamic component allows you to dynamically render any component based on the is kwarg. This is similar to Vue's dynamic components (<component :is>).

{% component "dynamic" is=table_comp data=table_data headers=table_headers %}
    {% fill "pagination" %}
        {% component "pagination" / %}
    {% endfill %}
{% endcomponent %}

The args, kwargs, and slot fills are all passed down to the underlying component.

As with other components, the dynamic component can be rendered from Python:

from django_components import DynamicComponent

DynamicComponent.render(
    kwargs={
        "is": table_comp,
        "data": table_data,
        "headers": table_headers,
    },
    slots={
        "pagination": PaginationComponent.render(
            render_dependencies=False,
        ),
    },
)

Dynamic component name

By default, the dynamic component is registered under the name "dynamic". In case of a conflict, you can set the COMPONENTS.dynamic_component_name setting to change the name used for the dynamic components.

# settings.py
COMPONENTS = ComponentsSettings(
    dynamic_component_name="my_dynamic",
)

After which you will be able to use the dynamic component with the new name:

{% component "my_dynamic" is=table_comp data=table_data headers=table_headers %}
    {% fill "pagination" %}
        {% component "pagination" / %}
    {% endfill %}
{% endcomponent %}