Theme
Version
GitHub PyPI Discord
On this page

Add slots

Options like heading work well for short values. When the person using a component should be able to pass whole pieces of HTML, such as a paragraph or a button, use slots: places in the template where outside content goes.

In this step you build a panel with one slot for its main content and another for an optional action. If no action is passed in, the panel shows fallback text.

Create the panel

Save this as reading_panel.py:

Named slots and fallback content
from citry import Component, SlotInput

class ReadingPanel(Component):
    class Kwargs:
        title: str

    class Slots:
        default: SlotInput
        footer: SlotInput | None = None

    template = """
      <section class="reading-panel">
        <h2>{{ title }}</h2>
        <div class="reading-panel__body">
          <c-slot />
        </div>
        <footer class="reading-panel__footer">
          <c-slot name="footer">
            No action needed.
          </c-slot>
        </footer>
      </section>
    """

class PanelPage(Component):
    class Kwargs:
        pass

    class Slots:
        pass

    template = """
      <main>
        <c-ReadingPanel title="Finished">
          <p>Kindred</p>
        </c-ReadingPanel>

        <c-ReadingPanel title="Up next">
          <c-fill name="default">
            <p>A Wizard of Earthsea</p>
          </c-fill>
          <c-fill name="footer">
            <button type="button">Start reading</button>
          </c-fill>
        </c-ReadingPanel>
      </main>
    """

page = PanelPage()

if __name__ == "__main__":
    print(page)

page

Run it:

python reading_panel.py

The first panel ends with β€œNo action needed.” The second ends with a β€œStart reading” button.

Place the slots

Inside ReadingPanel, each <c-slot> marks a place where content from outside goes. The arrows show where the content from PanelPage ends up:

<!-- Inside ReadingPanel (inside) -->
<div class="reading-panel__body">
  <c-slot />    β‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺ
</div>                               |
                                     |
<!-- Inside PanelPage (outside) -->  |
<c-ReadingPanel title="Finished">    |
  <p>Kindred</p>  ≫≫≫≫≫≫≫≫≫≫≫≫≫≫≫≫≫
</c-ReadingPanel>

The <p> is plain content inside <c-ReadingPanel>. Citry inserts it where <c-slot /> appears. Because that slot has no name, it is the default slot.

The footer slot has a name, and the outside content uses the same name:

<!-- Inside ReadingPanel (inside) -->
<footer class="reading-panel__footer">
  <c-slot name="footer">   β‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺβ‰ͺ
    No action needed.                   |
  </c-slot>                             |
</footer>                               |
                                        |
<!-- Inside PanelPage (outside) -->     |
<c-ReadingPanel title="Up next">        |
  <c-fill name="default">               |
    <p>A Wizard of Earthsea</p>         |
  </c-fill>                             |
  <c-fill name="footer">   ≫≫≫≫≫≫≫≫≫≫≫
    <button type="button">
      Start reading
    </button>
  </c-fill>
</c-ReadingPanel>

Each <c-fill> passes content to the slot with the same name. The button replaces β€œNo action needed.” When nothing fills the footer slot, that text stays as the fallback.

Declare the slots

Slots gives those two places names in Python:

class Slots:
    default: SlotInput
    footer: SlotInput | None = None

default has no = ... value after its type, so it is required. The footer defaults to None, so it is optional and the template's fallback text shows when it is empty.

SlotInput accepts any kind of slot content: HTML, text, another component, or a function that produces content.

Fill the slots

When you only fill the default slot, put the content directly inside the component tag:

<c-ReadingPanel title="Finished">
  <p>Kindred</p>
</c-ReadingPanel>

When you fill a named slot, wrap every piece of content in a <c-fill>, including the one for default:

<c-ReadingPanel title="Up next">
  <c-fill name="default">
    <p>A Wizard of Earthsea</p>
  </c-fill>
  <c-fill name="footer">
    <button type="button">Start reading</button>
  </c-fill>
</c-ReadingPanel>

Citry reports an error if you mix <c-fill> tags and loose content inside the same component tag.

Next steps

You can now pass Python values through Kwargs and whole pieces of content through slots. The Slots guide covers more, such as slots that pass data back to their content and filling slots from Python.

Next, add behavior that runs immediately in the browser.