Releases: django-components/django-components
Release list
0.151.1
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 indjc-corev1.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
What's Changed
Feat
- feat: hot-reload component files without server restart (#1657)
Full Changelog: 0.150.1...0.151.0
0.150.1
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
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
NoneSome third-party inclusion tags rely on Django accepting
Noneas 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 readget_css_data()values through CSS variables.See #1594.
-
The
on_renderhook documentation now renders Django-loaded templates with the correct context type.The example now uses the underlying template object when rendering with the Django
Contextpassed toon_render.See #1593.
Maintenance
0.149.0
What's Changed
-
Django's built-in
{% cache %}tag now works correctly inside component templatesWhen
{% 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_keyand 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.
New Contributors
- @jonathan-s made their first contribution in #1619
Full Changelog: 0.148.0...0.149.0
0.148.0
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.jsis now scoped by defaultUntil now, if you assigned a variable or declared a function in your
Component.jscode, 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
globalThiskeyword: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_dependencieshook to override JS/CSS renderingYou 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
ScriptandStyleobjects for this component (fromComponent.js/Component.css, JS/CSS variables, and Media). Return(new_scripts, new_styles)to replace them, orNoneto 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_dependencieshook to override JS/CSS renderingSay you want to add a CSP nonce to all scripts, or render scripts as
type="module".Before, you had to subclass a
Mediaclass 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_dependenciesextension hook that you can use to modify JS and CSS scripts before they are rendered.This hook exposes JS/CSS scripts as
ScriptandStyleobjects, 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, andStylehelper classesInstead of modifying the JS and CSS scripts as raw strings like
<script>and<style>,
the JS and CSS dependencies are represented by helper objects: -
Use
ScriptandStyleobjects inComponent.Media.js/Component.Media.cssYou can now put
ScriptandStyleobjects directly inComponent.Media.jsandComponent.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._templateremainsUNSET. See #1588
Refactor
-
Component.Media.js/cssnow render BEFOREComponent.js/css, instead of after. -
Rendering components in Python is now simpler: No explicit
deps_strategyneeded when nestedWhen you pre-render a component in Python, and pass it into another component's
get_template_data(),
you should passdeps_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. inget_template_data()),
you no longer need to passdeps_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
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, andancestorsproperties.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, orNoneif this is the root component.Component.root- Returns the root component instance (top-most ancestor), orselfif 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_createdhook 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
OnCssLoadedContextandOnJsLoadedContext -
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
classorstyleattributes 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_classand 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 fromRequestContextwhen Django has already runbind_template().See #1569
-
Fixed occasional
RuntimeError: Template not patchedmultithreading / concurrency issue. When django-components receives an unpatchedTemplateinstance, 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
What's Changed
django-components is now tested across all major browsers - Chromium, Firefox, WebKit.
Deprecations 🚨📢
-
In browser,
django-componentsinjectsComponentsglobal JavaScript object.This object has been renamed to
DjangoComponentsbecauseComponentsglobal is used in Firefox (#1544)For backwards compatibility, the old object name
Componentsis 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.
- Strings, numbers, booleans, and
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.jsorComponent.cssno 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
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
-
BaseNodeclass now has thefiltersandtagsattributes. 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 raisesSyntaxErrorinstead ofTypeError.{% 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
BaseNodeclass or@template_tagdecorator, 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
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 extraargsandkwargsarguments.
These arguments are passed to Django'sreverse()function.
New Contributors
- @barakharyati made their first contribution in #1511
Full Changelog: 0.143.2...0.144.0