{% extends "base.html" %} {% load common_extras %} {% comment %} The design system's living style guide, and the smoke view the L1-B acceptance criteria ask for: toast + ui_select + platform icons, all exercised on one page that is not the page they were built for. Note what is NOT here: a per-page toast include. Studio's toast host is a partial each template has to remember to pull in; this page fires toasts with nothing but a form posting to a toast_response view. {% endcomment %} {% block title %}Design system · DemandTrail Messaging{% endblock %} {% block page_header %}

Design system

The tokens, components and helpers every later UI issue renders into.

{% endblock %} {% block content %}

Colour roles

Orange is the primary action and the focus ring, nothing else. Selected and current are ink; state is one of five tones, always a dot and a word. test_colour_roles.py holds the line.

{% include "components/status_pill.html" with tone="success" label="Live" %} {% include "components/status_pill.html" with tone="warning" label="Offline" %} {% include "components/status_pill.html" with tone="info" label="Sending" %} {% include "components/status_pill.html" with tone="error" label="Failed" %} {% include "components/status_pill.html" with tone="neutral" label="Draft" %}

Tabs

.tabs at the top of a page (links, aria-current), .tabs-inline inside a pane (a tablist, aria-selected). Active is ink.

Summary cards

3Live
1Offline
2Draft

Table, meter and step dots

SequenceStepsActiveCompletionStatus
New subscriber welcome

Instagram · over 7 days

4 steps 182

64% complete

{% include "components/status_pill.html" with tone="success" label="Active" %}
October product update

Telegram

— 2,140

1,490 of 2,140

{% include "components/status_pill.html" with tone="info" label="Sending" %}

Option cards

Real radios, visually hidden: arrow keys move between cards. Selected is a 2px ink border.

Who receives it

Stepper

  1. {% include "partials/_nav_icon.html" with name="check" icon_size=13 %}Channel
  2. 2Audience
  3. 3Message
  1. 1TriggerWhen it starts
  2. 2ReplyWhat it sends

Empty state

{% include "components/empty_state.html" with icon="flows" title="No flows match" body="Try a different word, or clear the filters." action_label="Clear filters" action_click="0" %}

Toasts

Fired by toast_response(...) over HX-Trigger. The host lives in base.html, so this works on every page with no include.

Select

{% templatetag openblock %} ui_select {% templatetag closeblock %}: single and multi, with a panel that escapes the toolbar's overflow.

{% ui_select model="status" options=ui_demo_statuses placeholder="All statuses" icon="status" %} {% ui_select model="channels" options=ui_demo_channels multiple=True placeholder="Channels" icon="channel" %}

Platform icons

One partial, six platforms, currentColor. The last tile is an unknown key, proving the fallback glyph.

{% for platform in ui_demo_platforms %}
{% include "partials/_platform_icon.html" with platform=platform size="md" %} {% include "partials/_platform_icon.html" with platform=platform size="sm" %} {{ platform }}
{% endfor %}

.pi-chip-hero at size="lg" is the channel connect pages' mark. The radius has to be a class: .pi-chip sets border-radius unlayered, so a rounded-* utility beside it loses silently.

{% for platform in ui_demo_platforms %} {% include "partials/_platform_icon.html" with platform=platform size="lg" only %} {% endfor %}

Buttons

Alerts

Saved. Every tone has a matching toast.
Informational, for context a user did not ask for.
Warning, the fourth tone, which Studio's stylesheet is missing.
Something went wrong.

Form controls

Form fields

Label, control, hint, error: the shape every settings page repeats. Spacing between fields is .field's own margin; the control's width stays a call-site decision, which is why none of these classes sets one.

What your team sees in the workspace switcher.

Colours must be a 6-digit hex value like #3B82F6.

Setup steps, and a copy field

The channel connect pages' two pieces. Tailwind's preflight zeroes ol and nothing restored it, so these steps are the first numbered list in the product.

  1. Open @BotFather in Telegram and send /newbot.
  2. Choose a display name and a username ending in bot.
  3. Copy the token it replies with.
{% include "partials/_copy_field.html" with label="Deliveries arrive at" value="https://example.com/hooks/telegram/" hint="Reachable from the public internet over HTTPS." only %}
{% comment %} Everything below arrived with the redesign. The point of this page is that a component nobody can look at is a component nobody reviews, so the shared pieces go here the day they are written — not the per-surface .ib-* / .crm-* / .fb-* families, which only make sense in place. {% endcomment %}

Type scale

--fs-*, not --text-*: that prefix already holds the text colours, and it is Tailwind v4's @theme namespace.

Page title · 28
Section heading · 17
Lede and body copy · 15
Meta and table text · 13
Eyebrow · 12 uppercase

Surfaces

.card sets background, border and radius and nothing else: no padding, no display. This file is unlayered, so any property it set would be one no utility at the call site could ever override.

Card

The white surface everything sits on.

Tile

Sunken, for pickers and options.

Tile, chosen

The one they picked.

{% comment %}x-data of its own: this page may render without the app shell, whose scope every other trigger sits in.{% endcomment %}

Modal

Every create form lives in one: the button names the thing, the dialog asks for its name. {% templatetag openblock %} modal "id" title="…" close_on="event" {% templatetag closeblock %}, opened with $modal('id').

{% modal "demo-modal" title="New tag" %}
{% endmodal %}

Buttons and chips

State, in words

The dot carries the colour and the text carries the meaning. Bean green is ~2.8:1 on white, so it is a fill and never a label. --success-700 is the readable weight.

{% comment %} Each on its own line: .health is inline-flex, because in a table cell or beside a channel name it has to sit in the run of text. A wrapper is the right way to stack them, not a display override that would break every real call site. {% endcomment %}
Healthy · last message 4 minutes ago
Needs reconnecting · nothing received for 2 days
Connected, but nothing has arrived yet
Turned off · no messages sent or received

Avatars

M MK MK MK MK

Empty state

One, where every list used to hand-roll its own.

No flows here yet

A flow replies for you when something happens: a comment, a keyword, a reply to your story.

List row

{% include "partials/_nav_icon.html" with name="flows" icon_size=19 %}
Instagram comment → DM Live
When someone comments “price” · edited 2 hours ago
Open
{% include "partials/_nav_icon.html" with name="flows" icon_size=19 %}
Lead magnet, PDF delivery Draft
No trigger yet, so it will not run until you add one
Continue

Brand ramp

Bean green, the product's "good" colour since the redesign. 500 is a fill, 700 is text.

{% endblock %}