Pulse

Components

So far, we've been writing everything in a single component. As your application grows, you'll want to break it into smaller, reusable pieces. That's what Pulse components are for.

What is a Component?

A Pulse component is a reusable piece of UI that can have its own state and lifecycle. Components are created by decorating a function with @ps.component:

@ps.component
def Greeting(name: str):
    return ps.div(
        ps.h2(f"Hello, {name}!"),
        ps.p("Welcome to Pulse."),
    )

You can use this component anywhere in your app:

@ps.component
def page():
    return ps.div(
        Greeting(name="Alice"),
        Greeting(name="Bob"),
    )

Each Greeting instance is independent - they don't share state.

Component Props

Components can accept props (properties) as function arguments. Props are how parent components pass data to children:

@ps.component
def Card(title: str, subtitle: str = ""):
    return ps.div(className="border rounded p-4 shadow")[
        ps.h3(title, className="text-lg font-bold"),
        ps.p(subtitle, className="text-gray-600") if subtitle else None,
    ]

Use it like:

Card(title="Welcome")
Card(title="Settings", subtitle="Manage your preferences")

Component Children

Components can accept children using *children as a variadic argument:

@ps.component
def Card(title: str, *children, key=None):
    return ps.div(className="border rounded p-4 shadow")[
        ps.h3(title, className="text-lg font-bold"),
        *children,
    ]

With *children, you can use bracket syntax to pass child elements:

Card(title="User Profile")[
    ps.p("Name: Alice"),
    ps.p("Email: alice@example.com"),
]

This reads naturally - the card "contains" its children, just like HTML.

State in Components

Each component instance can have its own state. This is what makes components truly reusable:

class ToggleState(ps.State):
    on: bool = False

    def toggle(self):
        self.on = not self.on

@ps.component
def Toggle(label: str):
    with ps.init():
        st = ToggleState()

    return ps.button(
        f"{label}: {'ON' if st.on else 'OFF'}",
        onClick=st.toggle,
        className="px-3 py-1 rounded border",
    )

@ps.component
def settings():
    return ps.div(className="space-y-2")[
        Toggle(label="Wi-Fi"),
        Toggle(label="Bluetooth"),
        Toggle(label="Airplane Mode"),
    ]

Each toggle maintains its own state independently. Clicking one doesn't affect the others.

Component Keys

By default, Pulse identifies components by their position in the tree. This works fine for static layouts, but can cause problems when components move around (like in a list that can be reordered).

Keys tell Pulse which component is which, regardless of position:

@ps.component
def ListItem(text: str, key=None):
    with ps.init():
        st = ToggleState()

    return ps.div(className="flex items-center gap-2")[
        ps.input(type="checkbox", checked=st.on, onChange=st.toggle),
        ps.span(text),
    ]

@ps.component
def todo_list():
    items = ["Learn Pulse", "Build an app", "Ship it"]

    return ps.div()[
        [ListItem(text=item, key=item) for item in items]
    ]

With keys:

  • If you reorder items, each ListItem keeps its checkbox state
  • If you add an item at the start, existing items shift but keep their state

Without keys:

  • State is tied to position, not identity
  • Reordering or inserting can cause state to "jump" between items

Rule of thumb: Always use keys when rendering components from a list.

Complete Example: Reusable Card Component

Here's a complete example showing a reusable card component with props, children, and composition:

import pulse as ps

@ps.component
def Card(title: str, *children, key=None, variant: str = "default"):
    border_color = "border-blue-500" if variant == "primary" else "border-gray-200"

    return ps.div(className=f"border-2 {border_color} rounded-lg p-4 shadow-sm")[
        ps.h3(title, className="text-lg font-semibold mb-2"),
        ps.div(className="text-gray-700")[
            *children,
        ],
    ]

@ps.component
def StatCard(label: str, value: int, key=None):
    with ps.init():
        # Each stat card could have its own state if needed
        pass

    return Card(title=label, variant="primary")[
        ps.p(f"{value}", className="text-3xl font-bold text-blue-600"),
    ]

@ps.component
def dashboard():
    return ps.div(className="p-6 space-y-4")[
        ps.h1("Dashboard", className="text-2xl font-bold"),

        ps.div(className="grid grid-cols-3 gap-4")[
            StatCard(label="Users", value=1234, key="users"),
            StatCard(label="Orders", value=567, key="orders"),
            StatCard(label="Revenue", value=89012, key="revenue"),
        ],

        Card(title="Recent Activity")[
            ps.ul(className="list-disc list-inside")[
                ps.li("User signed up"),
                ps.li("Order placed"),
                ps.li("Payment received"),
            ],
        ],
    ]

app = ps.App([ps.Route("/", dashboard)])

This demonstrates:

  • Reusable Card with customizable title, children, and variant
  • Composed StatCard that uses Card internally
  • Keys for list-like grid items
  • Clean separation of concerns

See also

What to read next

On this page