Package: django-component-kit
Django Component KitIntroducing Django Component Kit, a small and non-intrusive Python library for Django that provides all the necessary
tools to build flexible components in a Django-like manner.By using standard Python decorators and Django custom template tags, you can easily create highly flexible components
with IDE-friendly tooling.ExampleTemplate Tag# templatetags/mycomponents.pyfromdjangoimporttemplatefromdjango.template.loaderimportget_templatefromdjango_component_kitimportcomponent_block_tagregister=template.Library()@register.tag@register.tag('endcard')@component_block_tag(get_template("mycomponents/card.html"))defcard(title:str)->dict:returndict(title=title)Template<!-- templates/mycomponents/card.html -->{% load django_component_kit %}<divclass="card shadow-lg p-3 bg-body rounded"><divclass="card-header"><h>class="card-title">{{ title }}}</h></div><divclass="card-body">{% render_slot slots.children %}</div><divclass="card-footer text-muted"><divclass="links float-end">{% render_slot slots.footer %}</div></div></div>Usage<!-- templates/myapp/index.html -->{% load django_component_kit %}<div>{% card title="Card title" %}<pclass="card-text">Foo</p>{% slot footer %}<ahref="#"class="card-link">Card link</a>{% endslot %}
  {% endcard %}</div>Result<!-- https://myapp.com/index.html --><div><divclass="card mb-3 shadow-lg p-3 bg-body rounded"><divclass="card-header"><hclass="card-title">Card title</h></div><divclass="card-body"><pclass="card-text">Foo</p></div><divclass="card-footer text-muted"><divclass="links float-end"><ahref="#"class="card-link">Card link</a></div></div></div></div>But Why?While exploring different frontend frameworks and concepts, I came across HTMX and Alpine.js, which I fell in love with
for creating nice frontends in Django. However, the Django Templating System lacked proper support for components,
making it difficult to use these frameworks effectively. Existing third-party libraries for components in Django were
either outdated or felt more like frameworks than libraries, requiring a steep learning curve.To address this, I decided to create Django Component Kit, which is based on the unobtrusive and flexible
django-web-components library developed by Mihail Cristian Dumitru Xzya. I aimed to integrate it into my component
library and enhance its feature set.During the integration process, I realized that significant modifications were needed to align with my vision. I ended
up creating Django Component Kit, which shares some codebase with django-web-components but adopts a different approach
to component development.Getting StartedInstallationpipinstalldjango-component-kitAdding to Your ProjectThere are two ways to use the library:Adding as a Django AppThis method allows for deeper integration but requires some standard Django configuration.Add the app tosettings.py# myproject/settings.pyINSTALLED_APPS=[...,'django_component_kit',]Optional: Adding as a Built-in ComponentThis method allows you to add the component as a built-in in your project'ssettings.pywithout forcing others to
install it.# myproject/settings.pyTEMPLATES=[{"OPTIONS":{"builtins":["django_component_kit.templatetags.components"],},},]Adding to Your Own Component LibraryIf you want to use Django Component Kit primarily to build your own components, you can add it to your component library
without requiring others to install it.Add to your component librarytemplatetags/mycomponents.py# templatetags/mycomponents.pyfromdjangoimporttemplatefromdjango_component_kit.tagsimportdo_merge_attrs,do_render_slot,do_slotregister=template.Library()register.tag("merge_attrs",do_merge_attrs)register.tag("render_slot",do_render_slot)register.tag("slot",do_slot)That's it! Now you can start creating your own components.UsageInline ComponentsInline components are simple elements without any children. They allow you to add attributes and are commonly used for
form fields or other end elements.1. Register a function with@component_inline_tag()Use the@component_inline_tag()decorator to register a function as a custom tag.# templatetags/mycomponents.pyfromdjangoimporttemplatefromdjango.formsimportFieldfromdjango.template.loaderimportget_templatefromdjango_component_kitimportcomponent_inline_tagregister=template.Library()@register.tag@component_inline_tag(get_template("mycomponents/text_input.html"))deftext_input(field:Field)->dict:"""Component for rendering a text input."""returndict(field=field)2. Create a template for the componentCreate a template that defines how the component should be rendered. Inline components receive the context provided by
the function and the parameters bundled into theattributesvariable.<!-- templates/mycomponents/text_input.html -->{% load django_component_kit %}<divclass="mb-3"><labelfor="{{ field.id_for_label }}"class="form-label">{{ field.label }}</label><inputtype="text"id="{{ field.id_for_label }}"name="{{ field.name }}"class="form-control"><divclass="form-text text-muted">{{ field.help_text }}</div></div>3. Use the component in a templateUse the component in your template by calling the registered tag and passing the required parameters.<!-- templates/myapp/index.html --><form>{% text_input field=form.field %}<buttontype="submit">Submit</button></form>ResultThe component will be rendered with the provided attributes:<!-- https://myapp.com/index.html --><form><divclass="mb-3"><labelfor="text_1"class="form-label">Foo</label><inputtype="text"id="text_1"name="Bar"class="form-control"><divclass="form-text text-muted">Batz</div></div><buttontype="submit">Submit</button></form>DetailsThe@component_inline_tag()decorator should be placed between the@register.tagdecorator and the function
definition.The decorator requires a template for rendering the component.All parameters passed to the function will be extracted from the context for further processing.The function must return a dictionary that will be used as the context for rendering the inline component.Parameters passed to the tag will be bundled into theattributesvariable in the template context.Theregister.tag()function provides IDE support for jumping from template tags to functions and viewing component
usage.Providing a parameter without a key will treat it as a boolean value. Seeattributesfor more details.Since: 0.5.0Optionally a context parameter can be added to a tag. This will contain the full contextBlock ComponentsBlock components behave similarly to inline components but allow for the inclusion of child elements, making them
suitable for more complex components.1. Register a function with@component_block_tag()Use the@component_block_tag()decorator to register a function as a custom tag.# templatetags/mycomponents.pyfromdjangoimporttemplatefromdjango.template.loaderimportget_templatefromdjango_component_kitimportcomponent_block_tagregister=template.Library()@register.tag@register.tag('endcard')@component_block_tag(get_template("mycomponents/card.html"))defcard()->dict:"""Component for rendering a card."""returndict()2. Create a template for the componentCreate a template that defines how the component should be rendered. Block components allow for the use of slots, which
are defined using theslottag.<!-- templates/mycomponents/card.html -->{% load django_component_kit %}<div{%merge_attrsattributesclass="card shadow-lg p-3 bg-body rounded"%}>{% if slots.header %}<divclass="card-header">{% render_slot slots.header %}</div>{% endif %}<divclass="card-body">{% render_slot slots.children %}</div>{% if slots.footer %}<divclass="card-footer text-muted"><divclass="links float-end">{% render_slot slots.footer %}</div></div>{% endif %}</div>3. Use the component in a templateUse the component in your template by calling the registered tag and defining the slots using theslottag.<!-- templates/myapp/index.html -->{% load django_component_kit %}<div>{% card class='mb-3' %}
    {% slot header %}<h>class="card-title">Card title</h>{% endslot %}<pclass="card-text">Bar</p>{% slot footer %}<ahref="#"class="card-link">Card link</a>{% endslot %}
    {% endcard %}</div>ResultThe component will be rendered with the provided attributes and slots:<!-- https://myapp.com/index.html --><div><divclass="card mb-3 shadow-lg p-3 bg-body rounded"><divclass="card-header"><hclass="card-title">Card title</h></div><divclass="card-body"><pclass="card-text">Foo</p></div><divclass="card-footer text-muted"><divclass="links float-end"><ahref="#"class="card-link">Card link</a></div></div></div></div>DetailsAll details of inline components apply to block components as well.The tag must end with anendXXXtag, whereXXXis the name of the component. Everything enclosed within the tags
will be added to the component.TheendXXXtag registration is optional but provides autocompletion and linking between the component and its usage.Theslotsvariable is a dictionary that contains all provided slots and a base slot calledchildren, which
collects all child elements not inside a slot.Missing variables within the template will not be rendered.Use therender_slottag to render the slots.Use the{% slot %}tag to define a slot. It must end with an{% endslot %}tag. Everything enclosed within the
tags will be added to the slot.Usemerge_attrsto merge attributes together.Since: 0.5.0Optionally a context parameter can be added to a tag. This will contain the full contextPassing Data to ComponentsYou can pass data to components using keyword arguments, which accept hardcoded values or variables:{% with error_message="Something bad happened!" %}
{% alert type="error" message=error_message %}
{% endwith %}All attributes will be added to theattributesdictionary, which will be available in the template context:{"attributes":{"type":"error","message":"Something bad happened!"}}You can then access the attributes from the component's template:<divclass="alert alert-{{ attributes.type }}">{{ attributes.message }}</div>Rendering All AttributesYou can also render all attributes directly using{{ attributes }}. For example, if you have the following component:{% alert id="alerts" class="font-bold" %} ... {% endalert %}You can render all attributes using:<div{{attributes}}><!-- Component content --></div>This will render the following HTML:<divid="alerts"class="font-bold"><!-- Component content --></div>Attributes with Special CharactersYou can pass attributes with special characters ([@:_-.]) as well as attributes with no value:{% button @click="handleClick" data-id="123" required %} ... {% endbutton %}This will result in the following dictionary available in the context:{"attributes":{"@click":"handleClick","data-id":"123","required":True,}}And will be rendered by{{ attributes }}as@click="handleClick" data-id="123" required.Default/Merged AttributesYou can specify default values for attributes or merge additional values into some of the component's attributes using
themerge_attrstag:<div{%merge_attrsattributesclass="alert"role="alert"%}><!-- Component content --></div>If we assume this component is used like this:{% alert class="mb-4" %} ... {% endalert %}The final rendered HTML of the component will be:<divclass="alert mb-4"role="alert"><!-- Component content --></div>Non-Class Attribute MergingWhen merging attributes that are notclassattributes, the values provided to themerge_attrstag will be considered
the "default" values of the attribute. These attributes will not be merged with injected attribute values but will be
overwritten. For example, abuttoncomponent's implementation may look like this:<button{%merge_attrsattributestype="button"%}>{% render_slot slots.inner_block %}</button>To render the button component with a customtype, you can specify it when consuming the component. If no type is
specified, the defaultbuttontype will be used:{% button type="submit" %} Submit {% endbutton %}The rendered HTML of thebuttoncomponent in this example would be:<buttontype="submit">Submit</button>Appendable AttributesYou can treat other attributes as "appendable" by using the+=operator:<div{%merge_attrsattributesdata-value+="some-value"%}><!-- Component content --></div>If we assume this component is used like this:{% alert data-value="foo" %} ... {% endalert %}The rendered HTML will be:<divdata-value="foo some-value"><!-- Component content --></div>Manipulating the AttributesBy default, all attributes are added to anattributesdictionary inside the context. However, in some cases, you may
want to separate certain attributes from theattributesdictionary. For example, you may want to render analertcomponent with a customidorclassattribute, while still being able to use additional attributes internally.To achieve this, you can manipulate the context in your component's function to provide a better API for using the
component:# templatetags/mycomponents.pyfromdjangoimporttemplatefromdjango.template.loaderimportget_templatefromdjango_component_kitimportcomponent_block_tagregister=template.Library()@register.tag@register.tag("endalert")@component_block_tag(get_template("mycomponents/alert.html"))defalert(dismissible:bool)->dict:"""Component for rendering an alert."""return{"dismissible":dismissible}The component's template can then be modified to handle the new structure:<!-- templates/mycomponents/alert.html -->{% load django_component_kit %}<div{%merge_attrsattributes%}>{% render_slot slots.children %}
    {% if dismissible %}<buttontype="button"class="btn-close"data-bs-dismiss="alert"aria-label="Close"></button>{% endif %}</div>This allows you to render the component like this:{% alert id="my-alert" class="my-class" dismissible %}
This is an alert.
{% endalert %}The rendered HTML will be:<divid="my-alert"class="my-class">This is an alert.<buttontype="button"class="btn-close"data-bs-dismiss="alert"aria-label="Close"></button></div>SlotsSlots allow you to pass additional content to your components. Theslotscontext variable is passed to your components
and consists of a dictionary with the slot name as the key and the slot content as the value. You can render the slots
inside your components using therender_slottag.The Default SlotBy default, any content not enclosed within a named slot will be added to the default slot, which is calledchildren.
You can render this slot using therender_slottag inside your component.Named SlotsSometimes a component may need to render multiple named slots in different locations. You can define named slots using
theslottag and render them using therender_slottag.Scoped SlotsSlots also have access to the component's context, allowing you to pass variables to the slot content. You can use
the:letattribute to bind the value passed torender_slotto a variable within the slot content.Nested ComponentsYou can nest components to create more complex elements. This allows you to build reusable and modular components.PartialsSince: 0.2.0Partials allow you to render only a part of a template. This can be useful, when you have a complex component which
consists of multiple subcomponents. Like a card with actions, which are used to manipulate the content of the card.
Usually you would use one template for the card and then one for each kind of action component. This is fine,
until you have javascript, HTMX or AlpineJS logic inside of these components, which must reference each other.
That's indeed possible, but the DX is bad, as you don't know what functions or attributes you can use and what they do.
In this case partials are coming in for the rescue. Just put all subcomponents into a partial inside the main
component. With this, even IDE support is present for seeing where which function is being called.Hint: The partials are cached, so you can use a lot of them without performance impact.Since: 0.3.0When a template is rendered as a partial, the 'is_partial' attribute is set.
Partials can be rendered directly via thePartialResponseclass.UsageYou can directly write the partial inside the main component at any point in the template. Only if theinlineargument
is set toTrue, the partial will be rendered inside the main component. If not, it will be hidden. The example uses
AlpineJS for demonstration purposes:<!-- templates/mycomponents/card.html -->{% load django_component_kit %}<divclass="card"x-data="{ color: text-success }"><divclass="card-header">{% render_slot slots.header %}
    {% partial "toggle_color_action" %}<buttontype="button"class="btn-close"aria-label="Toggle"@click="color=color==='text-success' ? 'text-danger' : 'text-success'">Toggle color</button>{% endpartial %}</div><divclass="card-body":class="color">{% render_slot slots.children %}</div></div># templatetags/mycomponents.pyfromdjangoimporttemplatefromdjango.template.loaderimportget_templatefromdjango_component_kitimportcomponent_block_tag,component_inline_tagregister=template.Library()@register.tag@register.tag('endcard')@component_block_tag(get_template("mycomponents/card.html"))defcard()->dict:returndict()@register.tag@component_inline_tag(get_template("mycomponents/card.html"),partial="toggle_color_action")deftoggle_color_action()->dict:returndict()<!-- templates/index.html -->{% load django_component_kit %}

{% card %}
  {% slot header %}
    {% toggle_color_action %}
  {% endslot %}<p>This is a text</p>{% endcard %}Result<divclass="card"x-data="{ color: text-success }"><divclass="card-header"><buttontype="button"class="btn-close"aria-label="Toggle"@click="color=color==='text-success' ? 'text-danger' : 'text-success'">Toggle color</button></div><divclass="card-body":class="color"><p>This is a text</p></div></div>AssetsSince: 0.3.0When registering a component, you can assign JS oder CSS assets to it. You can do this using thejsorcssarguments
in thecomponent_inline_tagorcomponent_block_tag. They will be ordered in the order they are registered,
CSS in front of JS. Duplicates are removed. You can then use the assets via theassetstag.Hint:If you want to modify the assets, you can import theassetsobject and modify it before usage.Usage# templatetags/mycomponents.pyfromdjangoimporttemplatefromdjango.template.loaderimportget_templatefromdjango_component_kitimportcomponent_inline_tagregister=template.Library()@register.tag@component_inline_tag(get_template("mycomponents/card.html"),js=["myapp/myjs.js"])defcard()->dict:returndict()<!-- templates/index.html -->{% assets %}Result<scriptsrc="static/myapp/myjs.js"defer></script>ContributionIf you have any questions, suggestions, or feedback, feel free
toopen an issue.Development and TestingTo set up the development environment and run the tests, follow these steps:Install the dependencies usingpoetry install.Activate the environment usingpoetry shell.Run the tests usingpoe test.Run the linting usingpoe lint.What's Next?The current goal is to stabilize Django Component Kit and ensure it works as expected in different applications and use
cases. Future plans include adding more features to the library, but there is no specific roadmap at the moment. One
potential addition could be support for assets, although the need for this is diminished with the popularity of
Tailwind, HTMX, and Alpine.js.