Theme
Version
GitHub PyPI Discord
On this page

Your first component

When the same piece of HTML appears in many places, you want to write it once and reuse it. In Citry, that reusable piece is a component: a Python class with an HTML template.

In this step you build a card with a colored top border. Each time you use it, you choose a color and put different content inside. The card runs with plain Python, so you do not need Django, FastAPI, or another web framework. It helps to recognize basic HTML, and you can copy the CSS as it is.

See the finished result before you start.

Create the card

Save this as component.py:

component.py
from citry import Component, SlotInput

class Card(Component):
    class Kwargs:
        accent: str

    class Slots:
        default: SlotInput

    def css_data(
        self,
        kwargs: Kwargs,
        slots: Slots,
    ) -> dict[str, str]:
        return {"accent": kwargs.accent}

    template = """
      <article class="demo-card">
        <c-slot />
      </article>
    """

    css = """
      .demo-card {
        max-width: 24rem;
        padding: 1rem 1.25rem;
        border: 1px solid
          color-mix(in srgb, currentColor 20%, transparent);
        border-top: 0.25rem solid var(--accent);
        border-radius: 8px;
        background: Canvas;
        color: CanvasText;
        font-family: system-ui, sans-serif;
      }

      .demo-card__title {
        margin: 0 0 0.25rem;
        font-size: 1.1rem;
      }

      .demo-card__body {
        margin: 0;
      }
    """

The next sections go through it one piece at a time.

Add a content slot

The template is ordinary HTML with one Citry tag:

<article class="demo-card">
  <c-slot />
</article>

<c-slot /> marks the place where the card's content appears. When you write this:

<c-Card accent="#8250df">
  <p>Hello from inside the card.</p>
</c-Card>

Citry puts the paragraph where <c-slot /> is. A place like this is called a slot. This one has no name, so it is the default slot.

Declare the inputs

The two short classes near the top of Card list what you can change each time you use it:

  • Kwargs lists the name=value options. This card has one option, accent, which sets the border color.
  • Slots lists the places where text or HTML can go. This card has one, default, for everything written between <c-Card> and </c-Card>.

Neither accent nor default has a fallback value, so you must provide both when you use the card.

Style the card

The css block belongs to the card. Citry adds it to any page that renders the card.

The css_data() method passes the chosen accent to that CSS as the variable --accent, and var(--accent) in the border rule reads it.

.demo-card is ordinary CSS: it styles every matching element on the page. Give component classes distinctive names so they do not style something else by accident.

Use the card in a template

Inside another component's template, the card looks like this:

<c-Card accent="#8250df">
  <h2 class="demo-card__title">Welcome</h2>
  <p class="demo-card__body">
    Choose the accent color, then add any content you like.
  </p>
</c-Card>

accent makes the top border purple. The heading and paragraph go inside the card because they sit between its opening and closing tags.

Use the card in Python

You can also create the card directly in Python. Save this as render.py next to component.py:

from component import Card

card = Card(
    accent="#8250df",
    slots={"default": "Build something useful."},
)
print(card)

The slots dictionary fills the default slot from Python. Run the file:

python render.py

Citry prints the card's HTML and the styles it needs. Shortened, the result looks like this:

<style>
  /* Citry adds --accent: #8250df for this card. */
  .demo-card {
    border-top: 0.25rem solid var(--accent);
  }
</style>
<article class="demo-card">
  Build something useful.
</article>

The real HTML has a few extra attributes that Citry uses. You do not need to write them.

Give each card a color

Several cards on one page share the same CSS rules, but each card keeps the color you gave it.

Save this as two_cards.py next to component.py:

from citry import Component

from component import Card

class CardList(Component):
    template = """
      <c-Card accent="#0969da">
        <p>Blue card: Prepare the first draft.</p>
      </c-Card>
      <c-Card accent="#bc4c00">
        <p>Orange card: Review the final copy.</p>
      </c-Card>
    """

card_list = CardList()
print(card_list)

Run python two_cards.py. Both cards use the same HTML and CSS, but one is blue and the other orange, each with its own text.

Catch input mistakes

If you forget the color, Citry cannot render the card:

card = Card(slots={"default": "Where is my color?"})
str(card)
# TypeError: Card.Kwargs.__init__() missing ... 'accent'

Add accent and the card renders:

card = Card(
    accent="#8250df",
    slots={"default": "Now the card has everything it needs."},
)
str(card)

Leaving out the default slot fails the same way. The error appears when the card turns into HTML, at str() or print(), not when Python first runs Card(...).

A value of the wrong type also fails when the card renders. accent: str rejects a number:

card = Card(accent=123, slots={"default": "Wrong color."})
str(card)
# TypeError: Card.Kwargs.accent expects str, got int (123). ...

Citry checks that accent is a string, not what the string says. Inputs and validation explains what each annotation accepts and how to check more.

Next steps

You now have a card that:

  • asks for an accent color;
  • places text or HTML where <c-slot /> appears;
  • can have a different color each time you use it; and
  • reports an error when required information is missing or has the wrong type.

You can open the Card recipe to compare the component, complete page, and live result. To continue, use Python data in components.