Theme
Version
GitHub PyPI Discord

Control flow

Use this pattern to render a list that can be empty, with each item drawn one of two ways. The task list repeats a row for each task with <c-for>, picks how a row looks with <c-if> and <c-else>, and shows a message with <c-empty> when there are no tasks.

"""A task list that shows c-for, c-if, and c-empty control flow."""

from typing import Any, TypedDict

from citry import Component

class Task(TypedDict):
    text: str
    done: bool

class TaskList(Component):
    """Renders a list of {text, done} tasks.

    One <c-if> check picks how a task looks: done tasks get a check
    mark and struck-through text, pending tasks a hollow bullet. When
    there are no tasks, the <c-empty> branch is shown.
    """

    class Kwargs:
        tasks: list[Task]
        title: str = "Tasks"

    class Slots:
        pass

    def template_data(
        self,
        kwargs: Kwargs,
        slots: Slots,
    ) -> dict[str, Any]:
        return {"tasks": kwargs.tasks, "title": kwargs.title}

    template = """
      <section class="tasklist">
        <h3 class="tasklist__title">{{ title }}</h3>
        <ul class="tasklist__items">
          <c-for each="task in tasks">
            <li class="tasklist__item">
              <c-if cond="task['done']">
                <span class="tasklist__mark tasklist__mark--done">
                  &#10003;
                </span>
                <span class="tasklist__text tasklist__text--done">
                  {{ task['text'] }}
                </span>
              </c-if>
              <c-else>
                <span class="tasklist__mark tasklist__mark--todo">
                  &#9675;
                </span>
                <span class="tasklist__text">{{ task['text'] }}</span>
              </c-else>
            </li>
          </c-for>
          <c-empty>
            <li class="tasklist__item tasklist__item--empty">
              Nothing to do yet.
            </li>
          </c-empty>
        </ul>
      </section>
    """

    css = """
      .tasklist {
        max-width: 24rem;
        margin: 0 0 1.25rem;
        padding: 1rem 1.25rem;
        border: 1px solid #d0d7de;
        border-radius: 8px;
        font-family: system-ui, sans-serif;
      }
      .tasklist__title {
        margin: 0 0 0.5rem;
        font-size: 1.05rem;
      }
      .tasklist__items {
        list-style: none;
        margin: 0;
        padding: 0;
      }
      .tasklist__item {
        display: flex;
        align-items: baseline;
        gap: 0.5rem;
        padding: 0.25rem 0;
      }
      .tasklist__mark--done {
        color: #1a7f37;
      }
      .tasklist__mark--todo {
        color: #8c959f;
      }
      .tasklist__text--done {
        color: #8c959f;
        text-decoration: line-through;
      }
      .tasklist__item--empty {
        color: #8c959f;
        font-style: italic;
      }
    """

<c-empty> goes right after the <c-for> it belongs to. The page renders the list twice: once with three tasks and once with none, so you can see both branches.

For every option of these tags, see Conditions and loops.