Skip to content
TÜV
SÜD
Component studioDjango / TCF
Dark

DESIGN SYSTEM / TCF INTEGRATION

Build with
confidence.

Explore every TÜV SÜD component.
See Django forms and server-side tables in action.

Explore components Try the workflow
82 native components2 themes100% local assets
LIVE PREVIEW TCF + TÜV SÜD
Ready for your next project
All systems ready Open demo

Native controls. Django validation. HTMX interactions.

01 / QUICK START

From template to interface.

Load the two tag libraries, use ts_* components, and let TCF load their local assets once.

A component with slots

{% load components tuvsud_theme %}
{% ts_card %}
  {% slot header %}Inspection{% endslot %}
  {% ts_button variant="primary" %}
    Continue
  {% endts_button %}
{% endts_card %}

Public component contracts

Native attributes
Use label, value, variant and other upstream attributes directly.
Slots
{% slot name %} assigns native named slots.
HTMX
get/post reverse Django routes. target is an element ID; swap chooses replacement behavior.
Themes
Apply ts-theme-light or ts-theme-dark to any container.
Django fields and reloadable regions
{% ts_form form=form post="save" target="editor" swap="outerHTML" %}
  {# Fields are rendered automatically. Explicit content replaces them. #}
{% endts_form %}
{% ts_field# field=form.email %}
{% ts_fields# form=form %}
{% ts_hx_content id="status" get="status" %}{% endts_hx_content %}
{# Reload with a body event named update_status. #}

02 / WORKING PATTERN

A complete HTMX workflow.

Search and sort 250 sample clients. Edit a row or create a client. Changes belong to your browser session.

Create in dialog Reset demo data Reload status

Create sample client

At least 3 characters.
Must be unique within your demo session.
Berlin Hamburg Munich Vienna
Inspection Certification Training
Active
Create client Reset

Client directory

All locations Berlin Hamburg Munich Vienna All statuses Active Inactive Apply filters
Clear selection
Sample clients
Details Actions
Reusable AJAX table component and backend
{% ts_ajax_table# id="demo-records" definition=table ajax="demo-data" form_source="record-filters" caption="Sample clients" multi_sort=True selectable=True responsive=True %}

from tcf.tables import Column, Table

class ClientTable(Table):
    searching = True
    columns = (Column('name', 'Name', order_by=('name',),
                      search_fields=('name',)),)
    def get_queryset(self):
        return Client.objects.filter(owner=self.request.user)

# In a CSRF-protected POST endpoint:
return ClientTable(request).response()
# Table prepares and renders only the requested page.
# Default maximum: 100 rows. Sort keys are allowlisted.
Forms in dialogs

Choose Create in dialog or Dialog edit in any row. The same session records, Django validation and table refresh are used. Errors stay in the open dialog; a successful save closes it and shows a toast.

{% ts_button get="demo-dialog-create" target="dialog-editor" swap="outerHTML" data-demo-dialog-form="create" %}Create in dialog{% endts_button %}
{% ts_dialog id="record-dialog" label="Client form" %}
  <div id="dialog-editor"></div>
{% endts_dialog %}

# Return demo/dialog_editor.html for GET and invalid POST.
# After a successful POST:
response['HX-Trigger'] = json.dumps({
    'update_demo-records': True,
    'demo-dialog-saved': {'message': 'Saved client.'}
})

The Dialog & drawer example below includes the matching JavaScript.

POST table-v1 → scoped records → search / order → bounded page → rendered HTML cells → TCFTable → TÜV SÜD adapter

03 / COMPONENT CATALOGUE

Every component. In context.

82 native elements and portable application helpers in 41 live examples. Related children appear with their parent. Expand each example for TCF code and slot names.

Actions

Buttons & links

Primary Default Danger Disabled
Save draft Continue Download report Save Print
List Grid
Read the quick start

icon adds a left icon; icon_left and icon_right select either or both sides. Native prefix/suffix slots also work. Use variant, size and disabled for actions; href creates a link. Icon-only buttons need a label.

Code & component reference 4 elements

ts_buttonSlots: default, prefix, suffix

ts_button_groupSlots: default

ts_icon_buttonSlots: default

ts_linkSlots: default

{% load components tuvsud_theme static %}
<div class="demo-inline">
{% ts_button variant="primary" %}Primary{% endts_button %}
{% ts_button %}Default{% endts_button %}
{% ts_button variant="danger" outline=True %}Danger{% endts_button %}
{% ts_button disabled=True %}Disabled{% endts_button %}
{% ts_icon_button# name="favorite" label="Favorite" %}
</div>
<div class="demo-inline" role="group" aria-label="Button icon examples">
{% ts_button id="demo-button-icon-left" icon="save" variant="primary" size="small" %}Save draft{% endts_button %}
{% ts_button id="demo-button-icon-right" icon_right="arrow_forward" %}Continue{% endts_button %}
{% ts_button id="demo-button-icon-both" icon_left="download" icon_right="arrow_forward" size="large" %}Download report{% endts_button %}
{% ts_button_save# id="demo-button-icon-helper" icon="save" type="button" %}
{% ts_button id="demo-button-icon-slots" %}{% slot prefix %}{% ts_icon# name="print" %}{% endslot %}Print{% endts_button %}
</div>
<div class="demo-group-scroll" tabindex="0" role="region" aria-label="View controls">
{% ts_button_group label="View" %}{% ts_button %}List{% endts_button %}{% ts_button %}Grid{% endts_button %}{% endts_button_group %}
</div>
{% ts_link href="#quickstart" %}Read the quick start{% endts_link %}
Actions

Dropdown & menu

Demo actions Inspect Download Unavailable Choose an action

Assign the trigger slot directly to a button. Listen for ts-select on the menu to handle an action.

Code & component reference 4 elements

ts_dropdownSlots: default, trigger

ts_menu

ts_menu_itemSlots: default, prefix, submenu, suffix

ts_menu_labelSlots: default

{% load components tuvsud_theme static %}
{% ts_dropdown %}
{% slot trigger %}{% ts_button caret=True %}Choose an action{% endts_button %}{% endslot %}
{% ts_menu %}{% ts_menu_label %}Demo actions{% endts_menu_label %}
{% ts_menu_item value="inspect" %}Inspect{% endts_menu_item %}
{% ts_menu_item value="download" %}Download{% endts_menu_item %}
{% ts_menu_item disabled=True %}Unavailable{% endts_menu_item %}{% endts_menu %}
{% endts_dropdown %}
Actions

Copy to clipboard

pip install 'tomcom-tcf[tuvsud-theme]'

value supplies copied text; ts-copy and ts-error report clipboard outcomes. Clipboard access needs localhost or HTTPS.

Code & component reference 1 element

ts_copy_buttonSlots: copy-icon, error-icon, success-icon

{% load components tuvsud_theme static %}
<code>pip install 'tomcom-tcf[tuvsud-theme]'</code>
{% ts_copy_button# value="pip install 'tomcom-tcf[tuvsud-theme]'" copy-label="Copy install command" %}
Forms

Text inputs

Set name, label and type. Controls emit ts-input while typing and ts-change when committed.

Code & component reference 2 elements

ts_inputSlots: clear-icon, help-text, hide-password-icon, label, label-icon, prefix, show-password-icon, suffix

ts_textareaSlots: help-text, label, label-icon

{% load components tuvsud_theme static %}
{% ts_input# name="preview-name" label="Name" placeholder="Ada Lovelace" clearable=True %}
{% ts_input# type="email" label="Email" placeholder="ada@example.test" %}
{% ts_input# type="password" label="Password" password-toggle=True value="example" %}
{% ts_textarea# label="Notes" rows="3" value="Native controls integrate with Django forms." %}
Forms

Select & combobox

Berlin Munich Vienna Inspection Certification Training

Options need stable values. multiple allows repeated values; combobox adds filtering as you type.

Code & component reference 3 elements

ts_selectSlots: default, clear-icon, expand-icon, help-text, label, label-icon, prefix, suffix

ts_optionSlots: default, prefix, suffix

ts_comboboxSlots: default, clear-icon, expand-icon, help-text, label, label-icon, no-options, prefix, suffix

{% load components tuvsud_theme static %}
{% ts_select label="Location" value="berlin" clearable=True %}
{% ts_option value="berlin" %}Berlin{% endts_option %}{% ts_option value="munich" %}Munich{% endts_option %}{% ts_option value="vienna" %}Vienna{% endts_option %}
{% endts_select %}
{% ts_combobox label="Find a service" placeholder="Type to filter" %}
{% ts_option value="inspection" %}Inspection{% endts_option %}{% ts_option value="certification" %}Certification{% endts_option %}{% ts_option value="training" %}Training{% endts_option %}
{% endts_combobox %}
Forms

Checkbox & switch

Send a confirmation Some locations selected Enable notifications

checked sets the initial state. Name/value are included by the native FormData integration.

Code & component reference 2 elements

ts_checkboxSlots: default, help-text, label-icon

ts_switchSlots: default, help-text, label-icon

{% load components tuvsud_theme static %}
{% ts_checkbox checked=True %}Send a confirmation{% endts_checkbox %}
{% ts_checkbox indeterminate=True %}Some locations selected{% endts_checkbox %}
{% ts_switch checked=True %}Enable notifications{% endts_switch %}
Forms

Radio groups

Inspection Training
Normal Urgent

Put radios in a group with a name and initial value. Button radios use the same group API.

Code & component reference 3 elements

ts_radioSlots: default, help-text

ts_radio_buttonSlots: default, prefix, suffix

ts_radio_groupSlots: default, help-text, label, label-icon

{% load components tuvsud_theme static %}
{% ts_radio_group label="Service" name="preview-service" value="inspection" %}
{% ts_radio value="inspection" %}Inspection{% endts_radio %}{% ts_radio value="training" %}Training{% endts_radio %}
{% endts_radio_group %}
<div class="demo-group-scroll" tabindex="0" role="region" aria-label="Priority controls">
{% ts_radio_group label="Priority" value="normal" direction="horizontal" %}
{% ts_radio_button value="normal" %}Normal{% endts_radio_button %}{% ts_radio_button value="urgent" %}Urgent{% endts_radio_button %}
{% endts_radio_group %}
</div>
Forms

Dates & calendar

Use ISO date values. locale controls display; min-date and max-date constrain selection.

Code & component reference 3 elements

ts_date_pickerSlots: default, label-icon

ts_date_rangeSlots: default, label-icon, label-icon-end

ts_calendar_static

{% load components tuvsud_theme static %}
{% ts_date_picker# label="Inspection date" value="2026-10-06" %}
{% ts_date_range# label-start="From" label-end="Until" value-start="2026-10-06" value-end="2026-10-12" %}
{% ts_calendar_static# value="2026-10-06" %}
Forms

Time picker

value uses HH:mm; minute-step limits the selectable interval.

Code & component reference 1 element

ts_time_pickerSlots: help-text, label, label-icon

{% load components tuvsud_theme static %}
{% ts_time_picker# label="Appointment" value="09:30" minute-step="15" %}
Forms

Range & rating

Use min/max/step for a range. Rating accepts value, max and precision and emits ts-change.

Code & component reference 2 elements

ts_rangeSlots: help-text, label

ts_rating

{% load components tuvsud_theme static %}
{% ts_range# label="Progress" min="0" max="100" value="65" %}
{% ts_rating# label="Rate this example" value="4" max="5" precision="0.5" %}
Forms

Color, IBAN & OTP

These specialized controls retain their native validation and events. OTP uses ts-otp-complete when every box is filled.

Code & component reference 3 elements

ts_color_pickerSlots: label

ts_iban

ts_otp_inputSlots: help-text, label

{% load components tuvsud_theme static %}
{% ts_color_picker# label="Accent color" value="#0066cc" %}
{% ts_iban# label="IBAN" value="DE89370400440532013000" %}
{% ts_otp_input# label="Verification code" length="6" value="123456" sms-autofill=False %}
Forms

Dropzone

Drag files or browse. This preview is local only; a real upload needs a multipart Django form and server validation.

Code & component reference 1 element

ts_dropzoneSlots: description, file-list, help-text, icon, label, label-icon

{% load components tuvsud_theme static %}
{% ts_dropzone# label="Supporting documents" help-text="Local preview only; files are not uploaded." multiple=True %}
Navigation

Breadcrumbs

Home Components Breadcrumb

Nest breadcrumb items inside a breadcrumb and provide href for navigable entries.

Code & component reference 2 elements

ts_breadcrumbSlots: default, separator

ts_breadcrumb_itemSlots: default, prefix, separator, suffix

{% load components tuvsud_theme static %}
{% ts_breadcrumb label="Demo navigation" %}
{% ts_breadcrumb_item href="#top" %}Home{% endts_breadcrumb_item %}
{% ts_breadcrumb_item href="#catalogue" %}Components{% endts_breadcrumb_item %}
{% ts_breadcrumb_item %}Breadcrumb{% endts_breadcrumb_item %}
{% endts_breadcrumb %}
Navigation

Tabs

Summary Locations Session

Loading summary...

Select this tab to fetch current data.

Select this tab to fetch current data.

Native ts-tab-show triggers HTMX on every activation, including keyboard navigation. Match each tab panel to its panel name.

Code & component reference 3 elements

ts_tabSlots: default

ts_tab_groupSlots: default, nav

ts_tab_panelSlots: default

{% load components tuvsud_theme static %}
{% ts_tab_group id="htmx-tabs" %}
{% ts_tab slot="nav" panel="overview" %}Summary{% endts_tab %}
{% ts_tab slot="nav" panel="locations" %}Locations{% endts_tab %}
{% ts_tab slot="nav" panel="activity" %}Session{% endts_tab %}
{% ts_tab_panel name="overview" %}
    {% ts_hx_content id="tab-overview-content" get="demo-tab-content" urlarg="overview" trigger="load, demo-tab-active" data-tab-content=True %}
    <p>Loading summary...</p>
    {% endts_hx_content %}
{% endts_tab_panel %}
{% ts_tab_panel name="locations" %}
    {% ts_hx_content id="tab-locations-content" get="demo-tab-content" urlarg="locations" trigger="demo-tab-active" data-tab-content=True %}
    <p>Select this tab to fetch current data.</p>
    {% endts_hx_content %}
{% endts_tab_panel %}
{% ts_tab_panel name="activity" %}
    {% ts_hx_content id="tab-activity-content" get="demo-tab-content" urlarg="activity" trigger="demo-tab-active" data-tab-content=True %}
    <p>Select this tab to fetch current data.</p>
    {% endts_hx_content %}
{% endts_tab_panel %}
{% endts_tab_group %}

JavaScript

/* Fetch on native activation, including keyboard navigation. */
const htmxTabs = document.getElementById('htmx-tabs');
htmxTabs.addEventListener('ts-tab-show', event => {
    if (event.target !== htmxTabs) return;
    const panel = Array.from(htmxTabs.querySelectorAll('ts-tab-panel'))
        .find(panel => panel.name === event.detail.name);
    const content = panel?.querySelector('[data-tab-content]');
    if (content) htmx.trigger(content, 'demo-tab-active');
});
Navigation

Stepper

First step Review your details Finished

current-step selects the active step. navigation enables built-in previous/next controls.

Code & component reference 2 elements

ts_stepperSlots: default

ts_stepSlots: default, icon, icon-done, icon-error, icon-warning

{% load components tuvsud_theme static %}
{% ts_stepper current-step="0" navigation=True %}
{% ts_step label="Details" description="Enter client details" %}First step{% endts_step %}
{% ts_step label="Review" description="Check your entries" %}Review your details{% endts_step %}
{% ts_step label="Complete" %}Finished{% endts_step %}
{% endts_stepper %}
Navigation

Tree

Services Inspection Certification Locations

Nest tree items for hierarchy; expanded opens a branch and selection controls selection behavior.

Code & component reference 2 elements

ts_treeSlots: default, collapse-icon, expand-icon

ts_tree_itemSlots: default, collapse-icon, expand-icon

{% load components tuvsud_theme static %}
{% ts_tree selection="single" %}
{% ts_tree_item expanded=True %}Services
{% ts_tree_item %}Inspection{% endts_tree_item %}{% ts_tree_item %}Certification{% endts_tree_item %}
{% endts_tree_item %}{% ts_tree_item %}Locations{% endts_tree_item %}
{% endts_tree %}
Navigation

Sidebar

Open sidebar
Overview Inspection Training

A bounded sidebar example.

Sidebars provide a persistent navigation panel and become a drawer on mobile. ts-select identifies the selected item.

Code & component reference 3 elements

ts_sidebarSlots: default, footer, header, label, search

ts_sidebar_groupSlots: default, icon, label

ts_sidebar_itemSlots: default, icon, suffix

{% load components tuvsud_theme static %}
{% ts_button data-demo-show="preview-sidebar" %}Open sidebar{% endts_button %}
<div class="demo-sidebar-frame">
{% ts_sidebar id="preview-sidebar" label="Workspace" style="--offset-top:0px;--size:15rem" collapsed=True %}
{% ts_sidebar_item value="overview" selected=True %}Overview{% endts_sidebar_item %}
{% ts_sidebar_group label="Services" open=True %}{% ts_sidebar_item value="inspection" %}Inspection{% endts_sidebar_item %}{% ts_sidebar_item value="training" %}Training{% endts_sidebar_item %}{% endts_sidebar_group %}
{% endts_sidebar %}<p>A bounded sidebar example.</p></div>
Navigation

Pagination

A standalone pagination item:

2

count is the page count, default-page is the initial page. ts-page-click carries detail.page.

Code & component reference 2 elements

ts_pagination

ts_pagination_item

{% load components tuvsud_theme static %}
{% ts_pagination# count="8" default-page="1" size="small" sibling-count="0" label="Preview pagination" %}
<p class="demo-muted">A standalone pagination item:</p>
{% ts_pagination_item page="2" %}2{% endts_pagination_item %}
Layout

Accordion

TCF tags render native TÜV SÜD custom elements. Yes. Use get, post, target and swap shorthands.

behavior="single" keeps one item open; named summary slots or summary attributes label each item.

Code & component reference 2 elements

ts_accordionSlots: default

ts_accordion_itemSlots: default, collapse-icon, expand-icon, summary

{% load components tuvsud_theme static %}
{% ts_accordion behavior="single" label="Frequently asked questions" %}
{% ts_accordion_item summary="How are components rendered?" open=True %}TCF tags render native TÜV SÜD custom elements.{% endts_accordion_item %}
{% ts_accordion_item summary="Can I use HTMX?" %}Yes. Use get, post, target and swap shorthands.{% endts_accordion_item %}
{% endts_accordion %}
Layout

Card & divider

Cards contain related information and actions.

Next appointment: 6 October.

Inspection overviewOpen workflow

Named header and footer slots group content. Divider separates adjacent regions.

Code & component reference 2 elements

ts_cardSlots: default, footer, header, image

ts_divider

{% load components tuvsud_theme static %}
{% ts_card %}
{% slot header %}<strong>Inspection overview</strong>{% endslot %}
<p>Cards contain related information and actions.</p>{% ts_divider# %}<p>Next appointment: 6 October.</p>
{% slot footer %}{% ts_button variant="primary" href="#workflow" %}Open workflow{% endts_button %}{% endslot %}
{% endts_card %}
Layout

Split panel

Preview
Details

Use start/end slots. Drag the divider or move it with the keyboard to resize adjacent panels.

Code & component reference 1 element

ts_split_panelSlots: divider, end, start

{% load components tuvsud_theme static %}
{% ts_split_panel position="45" style="height:180px" %}
{% slot start %}<div class="demo-split-pane">Preview</div>{% endslot %}
{% slot end %}<div class="demo-split-pane">Details</div>{% endslot %}
{% endts_split_panel %}
Layout

Composed table

Service Status Inspection Scheduled Training Complete

Static tables compose rows, columns and cells. Use ts_ajax_table for server-side search, sort and pagination.

Code & component reference 6 elements

ts_tableSlots: default, empty, footer, header

ts_rowSlots: default

ts_columnSlots: default

ts_cellSlots: default

ts_table_headerSlots: default

ts_table_footerSlots: default

{% load components tuvsud_theme static %}
{% ts_table striped=True %}
{% slot header %}{% ts_table_header# show-search=False show-page-size=False %}{% endslot %}
{% ts_row header=True %}{% ts_column field="name" %}Service{% endts_column %}{% ts_column field="status" %}Status{% endts_column %}{% endts_row %}
{% ts_row %}{% ts_cell %}Inspection{% endts_cell %}{% ts_cell %}Scheduled{% endts_cell %}{% endts_row %}
{% ts_row %}{% ts_cell %}Training{% endts_cell %}{% ts_cell %}Complete{% endts_cell %}{% endts_row %}
{% slot footer %}{% ts_table_footer# from="1" to="2" total="2" show-pagination=False %}{% endslot %}
{% endts_table %}
Feedback

Dialog & drawer

Open dialog Open drawer

Opening the dialog fetches fresh content.

Load services Close

A drawer gives additional context.

Close

ts-show loads fresh dialog content. HTMX swaps the inner region while the shell stays open. Successful form saves emit a close/toast event.

Code & component reference 2 elements

ts_dialogSlots: default, footer, header-actions, label

ts_drawerSlots: default, footer, header-actions, label

{% load components tuvsud_theme static %}
<div class="demo-inline">{% ts_button data-demo-show="preview-dialog" %}Open dialog{% endts_button %}{% ts_button data-demo-show="preview-drawer" %}Open drawer{% endts_button %}</div>
{% ts_dialog id="preview-dialog" label="HTMX inspection details" %}
    {% ts_hx_content id="preview-dialog-content" get="demo-dialog-content" urlarg="overview" trigger="demo-dialog-open" %}
    <p>Opening the dialog fetches fresh content.</p>
    {% endts_hx_content %}
    {% slot footer %}
    {% ts_button get="demo-dialog-content" urlarg="services" target="preview-dialog-content" %}Load services{% endts_button %}
    {% ts_button data-demo-hide="preview-dialog" variant="primary" %}Close{% endts_button %}
    {% endslot %}
{% endts_dialog %}
{% ts_drawer id="preview-drawer" label="More information" %}<p>A drawer gives additional context.</p>{% slot footer %}{% ts_button data-demo-hide="preview-drawer" %}Close{% endts_button %}{% endslot %}{% endts_drawer %}

JavaScript

/* Keep the dialog shell mounted; HTMX replaces only its content. */
const previewDialog = document.getElementById('preview-dialog');
previewDialog.addEventListener('ts-show', () => {
    htmx.trigger(document.getElementById('preview-dialog-content'), 'demo-dialog-open');
});

const recordDialog = document.getElementById('record-dialog');
document.addEventListener('click', event => {
    const trigger = event.target.closest('[data-demo-dialog-form]');
    if (!trigger) return;
    recordDialog.label = trigger.dataset.demoDialogForm === 'edit' ? 'Edit sample client' : 'Create sample client';
});

// Open after settling: an early modal can make the request source inert.
function openFormDialog(event) {
    const source = event.detail?.task?.sourceElement || event.detail?.requestConfig?.elt;
    if (source?.matches('[data-demo-dialog-form]')) recordDialog.show();
}
document.addEventListener('htmx:after:settle', openFormDialog);
document.addEventListener('htmx:afterSettle', openFormDialog);

// Django emits this event only after validation and a successful save.
document.body.addEventListener('demo-dialog-saved', async event => {
    await recordDialog.hide();
    const toast = document.getElementById('record-saved-toast');
    toast.querySelector('[data-toast-message]').textContent = event.detail.message;
    await toast.hide();
    await toast.show();
});
Feedback

Alert & toast

Inspection completed successfully.
Show alert Hide alert Show toast
Triggered from JavaScript.

Use show()/hide() from JavaScript. Toast placement creates a fixed stack; duration controls automatic dismissal.

Code & component reference 2 elements

ts_alertSlots: default, icon, suffix

ts_toastSlots: default, icon

{% load components tuvsud_theme static %}
{% ts_alert id="preview-alert" open=True closable=True variant="success" %}{% slot icon %}{% ts_icon# name="check_circle" %}{% endslot %}Inspection completed successfully.{% endts_alert %}
<div class="demo-inline">
{% ts_button id="show-alert" %}Show alert{% endts_button %}
{% ts_button id="hide-alert" %}Hide alert{% endts_button %}
{% ts_button id="show-toast" %}Show toast{% endts_button %}
</div>
{% ts_toast id="preview-toast" placement="bottom-right" duration="4000" closable=True variant="primary" %}Triggered from JavaScript.{% endts_toast %}

JavaScript

/* Native methods also work after Django/HTMX renders the elements. */
const previewAlert = document.getElementById('preview-alert');
const previewToast = document.getElementById('preview-toast');
document.getElementById('show-alert').addEventListener('click', () => previewAlert.show());
document.getElementById('hide-alert').addEventListener('click', () => previewAlert.hide());
document.getElementById('show-toast').addEventListener('click', async () => {
    await previewToast.hide();
    await previewToast.show();
});
Feedback

Badges & tags

12 Complete Pending
Inspection Berlin

Variants show status. Tags support removable and ts-remove for user-managed selections.

Code & component reference 2 elements

ts_badgeSlots: default

ts_tagSlots: default, prefix

{% load components tuvsud_theme static %}
<div class="demo-inline">{% ts_badge %}12{% endts_badge %}{% ts_badge variant="success" %}Complete{% endts_badge %}{% ts_badge variant="warning" %}Pending{% endts_badge %}</div>
<div class="demo-inline">{% ts_tag %}Inspection{% endts_tag %}{% ts_tag variant="primary" removable=True %}Berlin{% endts_tag %}</div>
Feedback

Progress & loading

65%

Use numeric value for determinate progress; spinner is indeterminate and needs an accessible label.

Code & component reference 4 elements

ts_progress_barSlots: default

ts_progress_ringSlots: default

ts_skeleton

ts_spinner

{% load components tuvsud_theme static %}
{% ts_progress_bar# value="65" label="Inspection progress" %}
<div class="demo-inline">{% ts_progress_ring value="65" %}65%{% endts_progress_ring %}{% ts_spinner# label="Loading preview" style="font-size:2rem" %}</div>
{% ts_skeleton# effect="sheen" style="height:1rem;width:80%" %}
{% ts_skeleton# effect="sheen" style="height:1rem;width:60%" %}
Feedback

Tooltip & popup

Hover or focus A custom popupAnchored content

Tooltips label an interactive child. Popup positions custom floating content using an anchor slot.

Code & component reference 2 elements

ts_tooltipSlots: default, content

ts_popupSlots: default, anchor

{% load components tuvsud_theme static %}
{% ts_tooltip content="More about inspection" %}{% ts_button %}Hover or focus{% endts_button %}{% endts_tooltip %}
{% ts_popup active=True placement="bottom" distance="8" style="position:relative" %}
{% slot anchor %}<span class="demo-anchor">Anchored content</span>{% endslot %}
<span class="demo-popup">A custom popup</span>{% endts_popup %}
Media

Avatars & icons

Avatars accept image or initials. Icons are served locally; provide label when an icon conveys meaning.

Code & component reference 3 elements

ts_avatarSlots: icon

ts_avatar_group

ts_icon

{% load components tuvsud_theme static %}
<div class="demo-inline">{% ts_avatar# initials="AB" label="Alex Becker" %}
{% ts_avatar_group max="3" %}{% ts_avatar# initials="AL" label="Ada Lovelace" %}{% ts_avatar# initials="GH" label="Grace Hopper" %}{% ts_avatar# initials="KT" label="Katherine Johnson" %}{% endts_avatar_group %}
{% ts_icon# name="verified" label="Verified" size="32" %}{% ts_icon# name="home" label="Home" size="32" %}</div>
Media

Image comparer & animated image

Before: blue landscapeAfter: green landscape

Image comparer uses before/after slots. Animated images can be paused without losing their alternative text.

Code & component reference 2 elements

ts_image_comparerSlots: after, before, handle

ts_animated_imageSlots: pause-icon, play-icon

{% load components tuvsud_theme static %}
{% ts_image_comparer %}
{% slot before %}<img src="{% static 'demo/scene-blue.svg' %}" alt="Before: blue landscape">{% endslot %}
{% slot after %}<img src="{% static 'demo/scene-green.svg' %}" alt="After: green landscape">{% endslot %}
{% endts_image_comparer %}
{% static 'demo/inspection.gif' as animation_url %}
{% ts_animated_image# src=animation_url alt="Animated blue inspection indicator" %}
Media

Animation

Animate
TÜV SÜD

name chooses a preset. play starts the animation; iterations="1" avoids continuous motion.

Code & component reference 1 element

ts_animationSlots: default

{% load components tuvsud_theme static %}
{% ts_button data-demo-animate="preview-animation" %}Animate{% endts_button %}
{% ts_animation id="preview-animation" name="bounce" duration="600" iterations="1" %}<div class="demo-animation-tile">TÜV SÜD</div>{% endts_animation %}
Utilities

Formatters & QR code

File size
Currency
Date
Relative time

Formatters use the browser locale; QR codes encode value. These examples use deterministic data.

Code & component reference 5 elements

ts_format_bytes

ts_format_date

ts_format_number

ts_relative_time

ts_qr_code

{% load components tuvsud_theme static %}
<dl class="demo-values"><dt>File size</dt><dd>{% ts_format_bytes# value="1572864" %}</dd>
<dt>Currency</dt><dd>{% ts_format_number# value="1234.5" type="currency" currency="EUR" %}</dd>
<dt>Date</dt><dd>{% ts_format_date# date="2026-10-06T09:30:00Z" day="numeric" month="long" year="numeric" %}</dd>
<dt>Relative time</dt><dd>{% ts_relative_time# date="2026-10-06T09:30:00Z" %}</dd></dl>
{% ts_qr_code# value="TUV SUD component demo" label="Demo QR code" size="100" %}
Utilities

Observers & accessibility

Watch attribute changes.
Change attribute
Drag the corner to resize.

Observer events appear here.

This sentence is available to screen readers.

Observers emit ts-mutation/ts-resize for child changes. Visually hidden content remains available to assistive technology.

Code & component reference 3 elements

ts_mutation_observerSlots: default

ts_resize_observerSlots: default

ts_visually_hiddenSlots: default

{% load components tuvsud_theme static %}
{% ts_mutation_observer attr="data-count" %}<div id="mutation-target" data-count="0">Watch attribute changes.</div>{% endts_mutation_observer %}
{% ts_button data-demo-mutate="mutation-target" %}Change attribute{% endts_button %}
{% ts_resize_observer %}<div class="demo-resize-box">Drag the corner to resize.</div>{% endts_resize_observer %}
<p class="demo-muted" id="observer-result" aria-live="polite">Observer events appear here.</p>
{% ts_visually_hidden %}This sentence is available to screen readers.{% endts_visually_hidden %}
Application helpers

Upload queue & retries

Session upload queue

Choose files Upload files List Grid

No files queued.

    CMONE multipart contract: file + obj_id, numeric ID response. Use retry.txt to simulate one failure. This demo retains metadata only.

    Code & component reference 1 element

    ts_file_uploaderSlots: uploaded, complete, title, files

    {% load components tuvsud_theme %}
    {% ts_file_uploader id="demo-uploader" upload_url="demo-upload" obj_id=1 accept=".txt,.pdf,.png,.jpg" max_file_size="2mb" get_file_url="/demo/files/" notify=True %}
        {% slot title %}Session upload queue{% endslot %}
    {% endts_file_uploader %}
    Application helpers

    Workflow graph & swimlanes

    Inspection workflow

    Drag to pan · Ctrl/⌘ + wheel to zoom

    Graph By team Arrange − 100% + Fit Fullscreen
    Scope and contact details. Client request
    Move Connect
    Check equipment and record findings. Inspection
    Move Connect
    Review and approve the result. Certification
    Move Connect

    Click a node or connection to inspect its event.

    Native cards and buttons inside the portable layout engine. Drag with the Move handle; Connect chooses a target node. Events report changes to your application.

    Code & component reference 2 elements

    ts_diagramSlots: title, toolbar, help, empty

    ts_diagram_nodeSlots: header, footer

    {% load components tuvsud_theme %}
    {% ts_diagram id="demo-graph" heading="Inspection workflow" edges=demo_edges lanes=demo_lanes movable=True connectable=True select_edges=True edge_labels=True %}
        {% slot toolbar %}{% ts_button size="small" data-demo-layout="graph" %}Graph{% endts_button %}{% ts_button size="small" data-demo-layout="swimlane" %}By team{% endts_button %}{% ts_button size="small" data-diagram-action="arrange" %}Arrange{% endts_button %}{% endslot %}
        {% ts_diagram_node node_id="request" lane_ids=demo_client_lane %}
            {% slot header %}Client request{% endslot %}Scope and contact details.
            {% slot footer %}<div class="demo-inline">{% ts_button size="small" data-diagram-drag-handle="" %}Move{% endts_button %}{% ts_button size="small" data-diagram-connect="" aria-pressed=False %}Connect{% endts_button %}</div>{% endslot %}
        {% endts_diagram_node %}
        {% ts_diagram_node node_id="audit" lane_ids=demo_inspector_lane %}
            {% slot header %}Inspection{% endslot %}Check equipment and record findings.
            {% slot footer %}<div class="demo-inline">{% ts_button size="small" data-diagram-drag-handle="" %}Move{% endts_button %}{% ts_button size="small" data-diagram-connect="" aria-pressed=False %}Connect{% endts_button %}</div>{% endslot %}
        {% endts_diagram_node %}
        {% ts_diagram_node node_id="release" lane_ids=demo_inspector_lane color="#008543" %}
            {% slot header %}Certification{% endslot %}Review and approve the result.
            {% slot footer %}<div class="demo-inline">{% ts_button size="small" data-diagram-drag-handle="" %}Move{% endts_button %}{% ts_button size="small" data-diagram-connect="" aria-pressed=False %}Connect{% endts_button %}</div>{% endslot %}
        {% endts_diagram_node %}
    {% endts_diagram %}<p id="demo-graph-events" role="status">Click a node or connection to inspect its event.</p>

    JavaScript

    /* Consumer-owned actions; framework helpers expose bubbling events. */
    document.addEventListener('click', event => {
        const button = event.target.closest('[data-demo-layout]');
        if (button) TCFTuvsudDiagram.get('demo-graph').setLayout(button.dataset.demoLayout, 'team');
    });
    for (const name of ['nodeClick','edgeClick','connect','nodeMoved']) {
        document.addEventListener('diagram:' + name, event => {
            if (event.detail.diagramId !== 'demo-graph') return;
            const {nodeId, edgeId, source, target} = event.detail;
            document.getElementById('demo-graph-events').textContent = name + ': ' + (nodeId || edgeId || source + ' → ' + target);
        });
    }
    for (const name of ['open','change','navigate']) {
        document.addEventListener('calendar:' + name, event => {
            if (event.detail.calendarId !== 'demo-calendar') return;
            const {id, start, date, mode} = event.detail;
            document.getElementById('demo-calendar-events').textContent = name + ': ' + (id ? id + (start ? ' · ' + start : '') : date + ' · ' + mode);
        });
    }
    document.addEventListener('table:selection', event => {
        if (!['demo-local-table','demo-records'].includes(event.target.id)) return;
        document.getElementById('demo-selection-events').textContent = 'Selected IDs: ' + (TCFTuvsudTables.selected(event.target.id).join(', ') || 'none');
    });
    
    Application helpers

    Resource scheduling

    ← → Day Week

    Time
    08:00 09:00 10:00 11:00 12:00 13:00 14:00 15:00 16:00 17:00
    Tue, 06 OctAlex · Inspector
    Safety audit09:00–10:00
    Site inspection09:30–11:00
    Tue, 06 OctSam · Inspector
    Training10:00–11:30

    Drag appointments to move them; drag their lower handle to resize. Keyboard: arrows to move, Shift + Up/Down to resize.

    Appointment changes are saved in your session.

    Switch day/week, navigate, move between resources, and resize appointments. Changes stay in this browser session. Use arrows to move and Shift + Up/Down to resize.

    Code & component reference 4 elements

    ts_calendar_gridSlots: toolbar

    ts_calendar_row

    ts_calendar_colSlots: header

    ts_calendar_event

    {% include 'demo/extras/calendar.html' %}<p id="demo-calendar-events" role="status">Appointment changes are saved in your session.</p>
    
    {% load components tuvsud_theme %}{% ts_calendar_grid# id="demo-calendar" date=demo_calendar_date mode=demo_calendar_mode|default:'day' resources=demo_resources events=demo_events editable=True url="demo-calendar" change_url="demo-calendar" %}

    JavaScript

    /* Consumer-owned actions; framework helpers expose bubbling events. */
    document.addEventListener('click', event => {
        const button = event.target.closest('[data-demo-layout]');
        if (button) TCFTuvsudDiagram.get('demo-graph').setLayout(button.dataset.demoLayout, 'team');
    });
    for (const name of ['nodeClick','edgeClick','connect','nodeMoved']) {
        document.addEventListener('diagram:' + name, event => {
            if (event.detail.diagramId !== 'demo-graph') return;
            const {nodeId, edgeId, source, target} = event.detail;
            document.getElementById('demo-graph-events').textContent = name + ': ' + (nodeId || edgeId || source + ' → ' + target);
        });
    }
    for (const name of ['open','change','navigate']) {
        document.addEventListener('calendar:' + name, event => {
            if (event.detail.calendarId !== 'demo-calendar') return;
            const {id, start, date, mode} = event.detail;
            document.getElementById('demo-calendar-events').textContent = name + ': ' + (id ? id + (start ? ' · ' + start : '') : date + ' · ' + mode);
        });
    }
    document.addEventListener('table:selection', event => {
        if (!['demo-local-table','demo-records'].includes(event.target.id)) return;
        document.getElementById('demo-selection-events').textContent = 'Selected IDs: ' + (TCFTuvsudTables.selected(event.target.id).join(', ') || 'none');
    });
    
    Application helpers

    Recursive service tree

    Services Inspection Certification Training
    Apply services Reset

    CMONE tree dictionaries become native tree items. Selected checkbox leaves submit their short_name; Reset restores their initial selection.

    Code & component reference 1 element

    ts_data_treeSlots: buttons

    {% load components tuvsud_theme %}
    {% ts_form id="demo-tree-form" post="demo-tree" target="demo-tree-result" %}
        {% ts_data_tree# id="demo-service-tree" tree=demo_tree %}
        {% slot buttons %}{% ts_button_save %}Apply services{% endts_button_save %}{% ts_button_reset# %}{% endslot %}
    {% endts_form %}<div id="demo-tree-result" aria-live="polite"></div>
    Application helpers

    Password policy checklist

    • At least 12 characters.
    • Use more than numbers.
    • Passwords match.

    The server validates the complete password policy when saving.

    Validate password Reset

    Django minimum-length and numeric rules are reflected in the checklist. Custom policies accept explicit rules; unsupported Django validators remain server-only.

    Code & component reference 1 element

    ts_password_strength

    {% include 'demo/extras/password.html' %}
    
    {% load components tuvsud_theme %}<div id="demo-password-editor">
    {% if message %}{% ts_alert variant="success" open=True %}{{ message }}{% endts_alert %}{% endif %}
    {% ts_form id="demo-password-form" form=demo_password_form post="demo-password" target="demo-password-editor" swap="outerHTML" %}
        {% ts_field# field=demo_password_form.password %}{% ts_field# field=demo_password_form.password_confirm %}
        {% ts_password_strength# id="demo-policy" password_name="password" confirmation_name="password_confirm" rules=demo_password_rules %}
        {% slot buttons %}{% ts_button_save %}Validate password{% endts_button_save %}{% ts_button_reset# %}{% endslot %}
    {% endts_form %}</div>
    Application helpers

    Cache inspection & clearing

    Refresh Clear all entries
    Cache / keyStatusCreatedUpdatedAction
    directory
    demo:directory
    Show values
    {'sample': True, 'items': 1}
    Stored 10/06/2026 11:08 p.m.10/06/2026 11:08 p.m. Clear
    statistics
    demo:statistics
    Show values
    {'sample': True, 'items': 2}
    Stored 10/06/2026 11:08 p.m.10/06/2026 11:08 p.m. Clear
    services
    demo:services
    Show values
    {'sample': True, 'items': 3}
    Stored 10/06/2026 11:08 p.m.10/06/2026 11:08 p.m. Clear

    Cache entries

    Preserves clear_all / clear_values action values. This example clears synthetic session entries, leaving the project cache untouched.

    Code & component reference 1 element

    ts_cache_panel

    {% include 'demo/extras/cache.html' %}
    
    {% load components tuvsud_theme %}{% ts_cache_panel# id="demo-cache" url="demo-cache" entries=demo_cache_entries cleared=cleared %}
    Application helpers

    Local table & persistent selection

    Clear selection
    Synthetic services
    Details

    Shift-click a second heading to add its sort priority. Selected IDs survive searches, pages and refreshes.

    Choose rows to see their stable record IDs.

    Inline records support client search, pagination, Shift-click multi-column sorting, selection by record ID, and mobile details. The live client directory above uses the same enhanced adapter with server data.

    Code & component reference 1 element

    ts_data_table

    {% load components tuvsud_theme %}
    {% ts_data_table# id="demo-local-table" columns=demo_local_columns rows=demo_local_rows page_size=5 multi_sort=True selectable=True responsive=True lazy=True caption="Synthetic services" %}
    <p class="demo-muted">Shift-click a second heading to add its sort priority. Selected IDs survive searches, pages and refreshes.</p>
    <p id="demo-selection-events" role="status">Choose rows to see their stable record IDs.</p>

    JavaScript

    /* Consumer-owned actions; framework helpers expose bubbling events. */
    document.addEventListener('click', event => {
        const button = event.target.closest('[data-demo-layout]');
        if (button) TCFTuvsudDiagram.get('demo-graph').setLayout(button.dataset.demoLayout, 'team');
    });
    for (const name of ['nodeClick','edgeClick','connect','nodeMoved']) {
        document.addEventListener('diagram:' + name, event => {
            if (event.detail.diagramId !== 'demo-graph') return;
            const {nodeId, edgeId, source, target} = event.detail;
            document.getElementById('demo-graph-events').textContent = name + ': ' + (nodeId || edgeId || source + ' → ' + target);
        });
    }
    for (const name of ['open','change','navigate']) {
        document.addEventListener('calendar:' + name, event => {
            if (event.detail.calendarId !== 'demo-calendar') return;
            const {id, start, date, mode} = event.detail;
            document.getElementById('demo-calendar-events').textContent = name + ': ' + (id ? id + (start ? ' · ' + start : '') : date + ' · ' + mode);
        });
    }
    document.addEventListener('table:selection', event => {
        if (!['demo-local-table','demo-records'].includes(event.target.id)) return;
        document.getElementById('demo-selection-events').textContent = 'Selected IDs: ' + (TCFTuvsudTables.selected(event.target.id).join(', ') || 'none');
    });
    
    Application helpers

    Create, dismiss & confirm actions

    Create example JavaScript confirmation HTMX confirmation

    Choose an action to see Cancel/OK confirmation.

    The HTMX preview loads only after OK.

    A native dialog can host a form or HTMX content.

    Confirm inside dialog

    This dialog stays open during confirmation.

    Cancel

    confirm supplies an HTMX question; Delete already confirms by default. Await TCFTuvsud.confirm() for JavaScript. Cancel, Escape or backdrop leave the action untouched; nested confirmations keep the parent dialog open.

    Code & component reference 2 elements

    ts_button_create

    ts_button_dismiss_modal

    {% load components tuvsud_theme %}
    <div class="demo-inline">
    {% ts_button_create id="helper-open-dialog" icon="add" data-demo-show="helper-action-dialog" %}Create example{% endts_button_create %}
    {% ts_button id="demo-confirm-js" icon="help" %}JavaScript confirmation{% endts_button %}
    {% ts_button id="demo-confirm-htmx" icon="refresh" get="demo-dialog-content" urlarg="overview" target="demo-confirm-result" confirm="Reload the preview content?" confirm_title="Reload preview" %}HTMX confirmation{% endts_button %}
    </div>
    <p id="demo-confirm-status" role="status">Choose an action to see Cancel/OK confirmation.</p>
    <div id="demo-confirm-result"><p>The HTMX preview loads only after OK.</p></div>
    {% ts_dialog id="helper-action-dialog" label="Create action" %}
        <p>A native dialog can host a form or HTMX content.</p>
        {% ts_button id="demo-confirm-nested" icon="help" %}Confirm inside dialog{% endts_button %}
        <p id="demo-confirm-nested-status" role="status">This dialog stays open during confirmation.</p>
        {% slot footer %}{% ts_button_dismiss_modal# icon="close" dialog="helper-action-dialog" %}{% endslot %}
    {% endts_dialog %}

    JavaScript

    /* Await the native dialog before performing an action. */
    for (const [buttonId, statusId] of [
        ['demo-confirm-js', 'demo-confirm-status'],
        ['demo-confirm-nested', 'demo-confirm-nested-status'],
    ]) {
        const button = document.getElementById(buttonId);
        button.addEventListener('click', async () => {
            const approved = await TCFTuvsud.confirm('Continue with this demo action?', {
                title: 'Confirm action', cancel: 'Cancel', ok: 'OK', trigger: button,
            });
            document.getElementById(statusId).textContent = approved
                ? 'Confirmed. The demo action ran.' : 'Cancelled. The demo action did not run.';
        });
    }
    

    Select Create in dialog or Dialog edit to load a Django form.

    Saved client.