Skip to content

Releases: django-components/django-components

0.151.1

Choose a tag to compare

@JuroOravec JuroOravec released this 25 Jun 10:38
5d597de

What's Changed

Fix

  • A multiline string value in a component tag no longer raises SyntaxError (#1669)

    Passing a quoted string that spans multiple lines as a component input
    (for example an Alpine.js or hyperscript handler) raised
    SyntaxError: unterminated string literal:

    {% component "button"
        _="on click
            set replyForm to closest <form />"
    %}{% endcomponent %}

    This affected plain strings only (those without {{ }} expressions) and
    was a regression introduced in djc-core v1.3.0. Such values render
    correctly again.

    This fix requires djc-core>=1.3.1, which is now the minimum version.

Full Changelog: 0.151.0...0.151.1

0.151.0

Choose a tag to compare

@JuroOravec JuroOravec released this 03 Jun 14:43
42bd406

What's Changed

Feat

  • feat: hot-reload component files without server restart (#1657)

Full Changelog: 0.150.1...0.151.0

0.150.1

Choose a tag to compare

@JuroOravec JuroOravec released this 01 Jun 15:36
e3cfff9

What's Changed

Fix

  • fix: pass origin to StringifiedNode (#1646)
  • Audit and fix latent finalizer / reimport bugs (follow-up to #1598/#1630) (#1647)
  • fix: stop CacheExtension leaking a cache key when a later extension short-circuits the render (#1648)
  • {% cache %} compat follow-ups: docs, callback leak fix, regression tests (#1651)

Docs

  • docs: warn against per-render state keyed by component ID in extensions (#1649)
  • docs: fix the relative benchmark links (#1653)

Full Changelog: 0.150.0...0.150.1

0.150.0

Choose a tag to compare

@EmilStenstrom EmilStenstrom released this 02 May 08:18

v0.150.0

2026-05-02

Compatibility

  • Supported Django versions now match the currently supported Django release line.

    Django 4.2 support was removed from the compatibility docs, package metadata, dependency floors, tox environments, and related compatibility code.

    See #1620.

Fix

  • Components now render correctly inside inclusion tags that return None

    Some third-party inclusion tags rely on Django accepting None as an empty context. django-components now handles that case before adding its internal dependency-rendering marker.

    See #1603.

  • Template expression errors now preserve the original traceback

    When Django template debug mode is enabled, errors raised from multi-node template expressions now surface the original rendering exception instead of being replaced by an unrelated traceback annotation error.

    See #1597.

  • Component registry cleanup no longer unregisters newer replacement components

    Registry finalizer callbacks no longer keep stale component classes alive or unregister a newer component class after a reimport replaces the old one.

    See #1598.

  • Moved documentation URLs now redirect to their current locations

    This helps search engines recover from stale indexed documentation URLs, including old release notes, contributing, development, and HTML/JS/CSS files pages.

    See #1355.

Docs

  • Docs builds now use strict link validation, and version-relative documentation links are preserved.

    This prevents broken internal docs links from slipping through CI while keeping links in older docs versions pointed at their own version instead of always pointing to latest.

    See #1343.

  • The JS and CSS variable examples now show the supported access patterns.

    JS examples now read get_js_data() values through $onComponent(), and CSS examples now read get_css_data() values through CSS variables.

    See #1594.

  • The on_render hook documentation now renders Django-loaded templates with the correct context type.

    The example now uses the underlying template object when rendering with the Django Context passed to on_render.

    See #1593.

Maintenance

  • Documentation deployment now avoids strict-build failures from generated release-note metadata warnings.

    See #1636.

  • Updated development and CI dependencies.

    See #1624, #1625, #1626, #1633, and #1634.

0.149.0

Choose a tag to compare

@EmilStenstrom EmilStenstrom released this 25 Apr 19:53

What's Changed

  • Django's built-in {% cache %} tag now works correctly inside component templates

    When {% cache %} wraps {% component %} inside another component, django-components now caches the fully rendered HTML instead of a first-pass placeholder.

    This fixes cache hits for nested components and also preserves compatibility when explicitly loading Django's cache tag library with {% load cache %}.

    See #1619.

  • Fixed memory leaks in component caching and request context-processor data reuse.

    This release cleans up stale entries in CacheExtension.render_id_to_cache_key and reuses cached request context-processor data without retaining unnecessary references.

    See #1613.

Refactor

  • Improved CI and release compatibility.

    The test matrix is 10x faster, documentation builds now avoid the Pygments 2.20 mkdocstrings regression, and several development dependencies were updated.

    See #1623 and #1611.

New Contributors

Full Changelog: 0.148.0...0.149.0

0.148.0

Choose a tag to compare

@JuroOravec JuroOravec released this 05 Feb 09:39
65ebebf

What's Changed

Modify the JS and CSS tags generated from components, JS code in Component.js no longer pollutes global scope, and using deps_strategy="ignore" inside get_template_data() is now optional.

Feat

  • JS from Component.js is now scoped by default

    Until now, if you assigned a variable or declared a function in your Component.js code, it would be available in the global scope. This could cause conflicts with other scripts on the page.

    Now, the JS code is scoped, so you can't accidentally assign global variables or functions.

    To define global variables or functions, you should instead use the globalThis keyword:

    globalThis.myGlobalVariable = "Hello, world!";
    
    globalThis.myGlobalFunction = () => {
        console.log("Hello, world!");
    };

    If you want to keep the original behaviour, set Script.wrap = False.

    Function wrapping does NOT apply to JS modules (type="module").

  • Component.on_dependencies hook to override JS/CSS rendering

    You can override Component.on_dependencies (a classmethod) to modify the JS/CSS dependencies emitted by that component only - for example to inject a CSP nonce, change attributes, or wrap inline JS.

    The hook receives lists of Script and Style objects for this component (from Component.js/Component.css, JS/CSS variables, and Media). Return (new_scripts, new_styles) to replace them, or None to leave them unchanged.

    To modify all dependencies for the whole page, use the extension hook instead.

    Example:

    from django_components import Component, Script, Style
    
    class MyButton(Component):
        @classmethod
        def on_dependencies(cls, scripts, styles):
            # Add a nonce to every inline style for this component
            for style in styles:
                if style.content and "nonce" not in style.attrs:
                    style.attrs["nonce"] = get_current_nonce()
            return (scripts, styles)
  • ComponentExtension.on_dependencies hook to override JS/CSS rendering

    Say you want to add a CSP nonce to all scripts, or render scripts as type="module".

    Before, you had to subclass a Media class to intercept how JS and CSS scripts are rendered.
    But this did not capture ALL JS and CSS scripts that are rendered.

    Now, there is a new on_dependencies extension hook that you can use to modify JS and CSS scripts before they are rendered.

    This hook exposes JS/CSS scripts as Script and Style objects, so you can modify them before they are rendered.

    You can add or remove attributes, add or remove entire scripts, and more. See Modifying JS / CSS scripts for more details.

    from django_components import ComponentExtension, OnDependenciesContext, Script, Style
    
    class MyExtension(ComponentExtension):
        def on_dependencies(self, ctx: OnDependenciesContext):
            scripts = list(ctx.scripts)
            styles = list(ctx.styles)
    
            # Set nonce attribute on all JS scripts
            for script in scripts:
                script.attrs["nonce"] = "1234567890"
    
            return (scripts, styles)
  • Dependency, Script, and Style helper classes

    Instead of modifying the JS and CSS scripts as raw strings like <script> and <style>,
    the JS and CSS dependencies are represented by helper objects:

    • Script

      • Represents a <script> tag.
      • Set Script.wrap = False to prevent classic JS from being wrapped in an IIFE.
    • Style

      • Represents a <style> tag or a <link rel="stylesheet"> tag.
      • When Style.url is set, renders as <link>, otherwise as <style> with
        inline content.
  • Use Script and Style objects in Component.Media.js / Component.Media.css

    You can now put Script and Style objects directly in Component.Media.js and Component.Media.css.

    from django_components import Component, Script, Style, register
    
    @register("calendar")
    class Calendar(Component):
        class Media:
            js = [
                Script(content="console.log('inline');")
            ]
            css = [
                Style(content=".x { color: red; }")
            ]

Fix

  • Fix race condition where ComponentMedia._template remains UNSET. See #1588

Refactor

  • Component.Media.js/css now render BEFORE Component.js/css, instead of after.

  • Rendering components in Python is now simpler: No explicit deps_strategy needed when nested

    When you pre-render a component in Python, and pass it into another component's get_template_data(),
    you should pass deps_strategy="ignore" to the render function to avoid rendering the dependencies twice.

    django-components now makes this easier for you.

    When you call Component.render() from Python inside another component (e.g. in get_template_data()),
    you no longer need to pass deps_strategy="ignore" to the inner Component. This is set automatically be default.

    Top-level renders still default to "document".

    See issue #1463.

    Before:

    class Outer(Component):
        def get_template_data(self, args, kwargs, slots, context):
            content = Inner.render(deps_strategy="ignore")
            return {"content": content}

    After:

    class Outer(Component):
        def get_template_data(self, args, kwargs, slots, context):
            content = Inner.render()  # no deps_strategy needed!
            return {"content": content}

Full Changelog: 0.147.0...0.148.0

0.147.0

Choose a tag to compare

@JuroOravec JuroOravec released this 02 Feb 10:52
0f29b0d

What's Changed

v0.147.0

Added support for Django 6.0, JS and CSS variables, and component tree navigation.

Breaking changes 🚨📢

  • Dropped support for Python 3.8 and 3.9.

  • Dropped support for Django 5.1.

Feat

  • JS variables with get_js_data()

    Pass data from Python to JavaScript with Component.get_js_data().

    Component.get_js_data() returns a dictionary. This data is automatically
    serialized to JSON and made available to your component's JavaScript code.

    In your JavaScript file, access these variables using the $onComponent() callback function.

    JS variables are automatically scoped to each component instance. Different
    instances of the same component can have different JS variable values.

    Example:

    from django_components import Component, register
    
    @register("product_card")
    class ProductCard(Component):
        template_file = "product_card.html"
        js_file = "product_card.js"
        css_file = "product_card.css"
    
        def get_js_data(self, args, kwargs: Kwargs, slots, context):
            product = Product.objects.get(id=kwargs.product_id)
            return {
                "product_id": kwargs.product_id,
                "price": float(product.price),
                "api_endpoint": f"/api/products/{kwargs.product_id}/",
            }
    // product_card.js
    
    // Access component JS variables in $onComponent callback
    $onComponent(({ product_id, price, api_endpoint }, ctx) => {
      const containerEl = ctx.els[0];
      containerEl.querySelector(".add-to-cart")
        .addEventListener("click", () => {
          fetch(api_endpoint, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ action: "add_to_cart", price: price }),
          });
        });
    });
  • CSS variables with get_css_data()

    Pass data from Python to CSS with get_css_data().

    get_css_data() returns a dictionary. This data is automatically
    converted to CSS custom properties and made available in your component's CSS.

    In your CSS file, access these variables using var().

    CSS variables are automatically scoped to each component instance, allowing different
    instances of the same component to have different variable values.

    Example:

    class ThemeableButton(Component):
        template_file = "button.html"
        css_file = "button.css"
    
        class Kwargs:
            theme: str = "default"
    
        def get_css_data(self, args, kwargs: Kwargs, slots, context):
            themes = {
                "default": {"bg": "#f0f0f0", "color": "#333"},
                "primary": {"bg": "#0275d8", "color": "#fff"},
                "danger": {"bg": "#d9534f", "color": "#fff"},
            }
            css_vars = themes.get(kwargs.theme, themes["default"])
            return css_vars
    /* button.css */
    .button {
      background-color: var(--bg);
      color: var(--color);
    }
  • Component tree navigation: parent, root, and ancestors properties.

    Components can now access their parent component, root component, and all ancestors
    during rendering. These properties are part of the Render API and allow components
    to navigate the component tree.

    • Component.parent - Returns the parent component instance, or None if this is the root component.
    • Component.root - Returns the root component instance (top-most ancestor), or self if this is the root.
    • Component.ancestors - Returns an iterator that yields all ancestor component instances, walking up the tree.

    Example:

    class Theme(Component):
        ...
    
    class Table(Component):
        def get_template_data(self, args, kwargs, slots, context):
            if self.parent is not None:
                # This component is nested in another component
                # Access parent's data
                if isinstance(self.parent, Theme):
                    theme_color = self.parent.kwargs.get("color", "default")
                    ...

    See #1252

  • New on_extension_created hook for extensions.

    New hook is called when the extension class is instantiated during Django startup.

    Use this hook to perform initialization logic that needs to run at server startup.

    Example:

    from django_components.extension import ComponentExtension, OnExtensionCreatedContext
    
    class MyExtension(ComponentExtension):
        name = "my_extension"
    
        def on_extension_created(self, ctx: OnExtensionCreatedContext) -> None:
            # Perform initialization logic
            import_my_modules()
            setup_global_state()

Fix

  • Add missing export of OnCssLoadedContext and OnJsLoadedContext

  • Fixed bug where Python expressions in template tags were not evaluated correctly
    when the expression was on a separate line.

    See #1255

    {% component "ListItem"
      attrs:class="
        {{ module_classes }}
        project-nav--item
        w-full mt-0 shadow
      "
    / %}
  • Fixed bug where multiple attributes with the same name (e.g., class) were not being merged
    correctly in {% html_attrs %}.

    See #1281

    Previously, when using duplicate class or style attributes with both variables and
    literals, only the first value was used. Now all values are properly merged:

    {% html_attrs attrs
      class=btn_class
      class="inline-flex w-full text-sm font-semibold"
    %}

    Both btn_class and the literal string are now merged together.

  • Fixed bug when {% component %} is used inside a {% block %} tag with {% extends %},
    causing context processor data to be duplicated and shadowed. Components now reuse context
    processor data from RequestContext when Django has already run bind_template().

    See #1569

  • Fixed occasional RuntimeError: Template not patched multithreading / concurrency issue. When django-components receives an unpatched Template instance, it now
    logs a warning and patches the template class on-the-fly instead of raising.

    See #1571

Full Changelog: 0.146.0...0.147.0

0.146.0

Choose a tag to compare

@JuroOravec JuroOravec released this 23 Jan 09:03
7177315

What's Changed

django-components is now tested across all major browsers - Chromium, Firefox, WebKit.

Deprecations 🚨📢

  • In browser, django-components injects Components global JavaScript object.

    This object has been renamed to DjangoComponents because Components global is used in Firefox (#1544)

    For backwards compatibility, the old object name Components is still available, and will be removed in v1.0.

Feat

  • Python expressions in template tags. Evaluate Python code directly in template by wrapping expressions in parentheses:

    {% component "button"
      disabled=(not editable)
      variant=(user.is_admin and 'danger' or 'primary')
    / %}

    Python expressions provide a Vue/React-like experience for writing component templates.

    Expressions run in a safe sandboxed environment same to Jinja.

    These allow you to perform simple transformations like:

    • Negating booleans
    • Conditional expressions
    • Method calls
    • Arithmetic operations
    • And more...

    Read more in the Python expressions documentation.

  • Literal lists and dictionaries in template tags. Pass structured data directly in templates:

    {% component "table"
        headers=["Name", "Age", "Email"]
        data=[
            {"name": "John", "age": 30, "email": "john@example.com"},
            {"name": "Jane", "age": 25, "email": "jane@example.com"},
        ]
    / %}

    Lists and dictionaries can contain the same values as template tag attributes:

    • Strings, numbers, booleans, and None
    • Python expressions
    • Template variables
    • Nested lists and dictionaries
    • Nested templates with {{ }} and {% %} syntax

    Each value can have filters applied to it.

    Read more in the Literal lists and dictionaries documentation.

Fix

  • There was bug where, when you defined Kwargs, Args, Slots, etc, as @dataclass,
    and one of its fields was a dataclass object again, the nested dataclass was incorrectly
    converted to a dictionary, instead of being left as a dataclass instance.

    Before:

    from dataclasses import dataclass
    from django_components.util.misc import to_dict
    
    @dataclass
    class User:
        name: str
    
    class MyTable(Component):
        @dataclass
        class Kwargs:
            user: User
            count: int
    
        def get_template_data(self, args, kwargs: Kwargs, slots, context):
            # INCORRECT!!! Should be `User(name="John")`
            assert kwargs.user == { "name": "John" }

Refactor

  • Component's JS, CSS, and HTML template are now loaded independently.

    This means that accessing Component.js or Component.css no longer triggers template resolution.

    This should improve server startup time as fewer files need to be loaded up front.

Docs

  • The "dynamic expressions" were renamed to "nested templates"

Full Changelog: 0.145.0...0.146.0

0.145.0

Choose a tag to compare

@JuroOravec JuroOravec released this 21 Jan 16:37
b9db5dc

What's Changed

Perf

Rendering components is now ~20% faster, thanks to:

  • using Rust-based template tag parsing
  • skipping eager input validation in favour of on-exception handling

Feat

  • BaseNode class now has the filters and tags attributes. These dictionaries keep track of what filters and tags can be used within the {% %} tag.

    Extensions can use these attributes to add custom filters and tags to the tag.

Refactor

  • In template, when a component tag has a positional argument
    after a keyword argument it now raises SyntaxError instead of TypeError.

    {% mytag 'John' msg='Hello' 123 %}
  • When a component tag receives multiple kwargs with the same name, it no longer raises TypeError.

    Instead, the later kwargs overwrite the earlier ones.

    {% mytag 'John' x=123 x=456 %}
  • All template tags ({% component %}, {% slot %}, etc.) now include the exact tag (as found in the template) in the error message when an error occurs:

    TypeError: Error in mytag: missing 1 required keyword-only arguments: 'msg'
        1 | {% mytag 'John' %}
            ^^^^^^^^^^^^^^^^^^
  • When creating custom template tags with BaseNode class or @template_tag decorator, you now get an error when
    the tag name is the same as one of the flags:

    class SlotNode(BaseNode):
        tag = "slot"
        allowed_flags = ["slot"]  # Raises!

Full Changelog: 0.144.0...0.145.0

0.144.0

Choose a tag to compare

@JuroOravec JuroOravec released this 18 Jan 18:46
84d1d48

What's Changed

Feat

  • Component URLs can now be customized with route parameters (By @alexandreMartinEcl in #1523)

    • Each component class has its own URL that triggers the component's HTTP handlers.
    • By default, the route path is components/{component.class_id}/.
    • You can now customize the route path by overriding Component.View.get_route_path().
    from django_components import Component, get_component_url
    
    class UserProfile(Component):
        class View:
            @classmethod
            def get_route_path(cls):
                return f"users/<str:username>/<int:user_id>/"
    
            def get(self, request, username: str, user_id: int, **kwargs):
                return UserProfile.render_to_response()
    
    # Get the URL with route parameters filled
    url = get_component_url(
        UserProfile,
        kwargs={"username": "john", "user_id": 42},
    )
    # /components/ext/view/users/john/42/

Refactor

  • get_component_url() now accepts extra args and kwargs arguments.
    These arguments are passed to Django's reverse() function.

New Contributors

Full Changelog: 0.143.2...0.144.0