Skip to content

Elements in PyWeber

Elements are the fundamental building blocks of PyWeber applications, representing HTML elements with a powerful object-oriented API for DOM manipulation.

Read this first

Before deep-diving here, read the Element model guide — it explains how childs, content, and {{placeholders}} stay in sync. Most ordering bugs come from skipping that page.

Creating Elements

Elements can be created programmatically or retrieved from templates:

import pyweber as pw

# Create a button element
button = pw.Element(
    tag="button",
    id="submit-btn",
    classes=["btn", "primary"],
    content="Submit",
    attrs={"type": "submit"}
)

# Create a nested structure
form = pw.Element(
    tag="form",
    id="contact-form",
    childs=[
        pw.Element(tag="input", attrs={"type": "text", "name": "name"}),
        pw.Element(tag="input", attrs={"type": "email", "name": "email"}),
        button
    ]
)

Element Properties

Property Type Description
tag str HTML tag name (e.g., "div", "button")
id str Element ID attribute
classes list[str] List of CSS classes
content str Text content of the element
value str Value attribute (for form elements)
style dict[str, str] Dictionary of inline styles
attrs dict[str, str] Dictionary of HTML attributes
childs list[Element] List of child elements
parent Element Parent element
events TemplateEvents Event handlers for this element
uuid str Unique identifier (auto-generated)
data Any Custom data that can be attached to the element
clone property Returns a deep copy of the element with all children

Cloning Elements

PyWeber provides a powerful cloning mechanism to create independent copies of elements:

# Create an element with children
original = pw.Element(
    tag="div",
    classes=["container"],
    childs=[
        pw.Element(tag="p", content="Hello world"),
        pw.Element(tag="button", content="Press")
    ]
)

# Create an independent deep copy
copy = original.clone

# Modify the copy without affecting the original
copy.add_class("main")
copy.childs[0].content = "Modified content"

The clone property performs a deep copy of the entire element tree, ensuring that: - All properties are copied - All children are cloned recursively - UUIDs are preserved for consistent referencing - Parent-child relationships are maintained

Advanced Content with Child Elements

PyWeber combines text content with child elements using {{uuid}} placeholders (double braces):

icon = pw.Element(tag='i', classes=['bi', 'bi-play-fill'])
button = pw.Element(tag='button', classes=['btn', 'primary'], childs=[icon])
# Placeholder uses the icon's UUID — registered automatically when assigning childs
button.content = f"{{{{{icon.uuid}}}}} Start Process"

You can also pass slots via kwargs:

middle = pw.Element('em', content='middle')
parent = pw.Element(
    'div',
    childs=[pw.Element('span', content='A'), '{{middle}}', pw.Element('span', content='B')],
    middle=middle,
)

See Element model for full details.

Form Components

PyWeber provides pre-built components for common form elements with proper HTML attributes:

# Text input
username = pw.InputText(
    name="username",
    id="user-input",
    placeholder="Enter username",
    required=True,
    autocomplete="username"
)

# Password input with toggleable visibility
password = pw.InputPassword(
    name="password",
    id="password-input",
    placeholder="Enter password",
    showpassword=False,
    autocomplete="current-password"
)

# Text area
comments = pw.TextArea(
    name="comments",
    id="comments-area",
    placeholder="Enter your comments",
    rows=5,
    cols=40
)

# Other input types
email = pw.InputEmail(name="email", required=True)
number = pw.InputNumber(name="quantity", min=1, max=10)
checkbox = pw.InputCheckbox(name="terms", value="accept", checked=False)
date = pw.InputDate(name="birthdate")
file = pw.InputFile(name="upload", accept=".pdf,.jpg")

All form components properly handle their specific attributes and events, ensuring correct HTML rendering and behavior.

Creating Custom Components

You can create reusable components by extending the Element class:

class IconButton(pw.Element):
    def __init__(self, id: str, content: str, icon_classes: list[str], events: pw.TemplateEvents = None):
        super().__init__(tag="button", id=id, events=events)

        # Create icon element
        self.icon = pw.Element(
            tag="i",
            classes=icon_classes
        )

        # Add icon as child
        self.childs = [self.icon]
        self.content = f"{{{{{self.icon.uuid}}}}} {content}"
Usage:
# Create a play button
play_button = IconButton(
    id="play-btn",
    content="Play",
    icon_classes=["bi", "bi-play-fill"],
    events=pw.TemplateEvents(onclick=self.handle_play)
)

Working with Classes

# Add classes
element.add_class("primary")
element.add_class("large bold")  # Add multiple classes

# Remove classes
element.remove_class("secondary")
element.remove_class("small italic")  # Remove multiple classes

# Check if element has a class
if element.has_class("active"):
    print("Element is active")

# Toggle classes (add if not present, remove if present)
element.toggle_class("selected")
element.toggle_class("expanded collapsed")  # Toggle multiple classes

Working with Styles

# Set styles
element.set_style("color", "blue")
element.set_style("font-size", "16px")

# Get styles
color = element.get_style("color")
font_size = element.get_style("font-size", "12px")  # Default if not set

# Remove styles
element.remove_style("background-color")

Working with Attributes

# Set attributes
element.set_attr("data-id", "123")
element.set_attr("aria-label", "Submit form")

# Get attributes
data_id = element.get_attr("data-id")
aria_label = element.get_attr("aria-label", "Button")  # Default if not set

# Remove attributes
element.remove_attr("disabled")

Managing Child Elements

# Add a child element
new_child = pw.Element(tag="span", content="Child text")
parent.add_child(new_child)

# Remove a child element
parent.remove_child(child)

# Remove a child at specific index
parent.pop_child(0)  # Remove first child
parent.pop_child()   # Remove last child

# Access children
first_child = parent.childs[0]
last_child = parent.childs[-1]

Element Selection

Elements provide methods to find child elements:

# CSS selectors (preferred)
button = element.querySelector("#submit-button")
items = element.querySelectorAll(".item")

# Attribute-based lookup
from pyweber.utils.types import GetBy

element.getElement(by=GetBy.ID, value="header")
element.getElements(by=GetBy.CLASSES, value="card")
element.getElements(by=GetBy.ATTRS, value="type:submit")
element.getElements(by=GetBy.STYLE, value="color:blue")

Deprecated methods removed in 1.0.2+

getElementById, getElementByClass, and getElementByUUID on templates were replaced by getElement / getElements with GetBy.

Event Handling

Elements can respond to user interactions through event handlers:

from pyweber.utils.types import EventType

# Method 1: Using events property
button.events.onclick = self.handle_click
input.events.oninput = self.validate_input

# Method 2: Using properties directly on form elements
text_input.onchange = self.handle_change
text_input.onfocus = self.handle_focus
text_input.onblur = self.handle_blur

# Method 3: Using add_event method
button.add_event(EventType.CLICK, self.handle_click)
form.add_event(EventType.SUBMIT, self.handle_submit)

# Remove events
button.remove_event(EventType.CLICK)

Efficient DOM Updates with TemplateDiff

PyWeber uses an intelligent diffing algorithm to efficiently update the DOM. This runs automatically when you call e.update() — you rarely need to use TemplateDiff directly.

from pyweber.models.template_diff import TemplateDiff

original = pw.Element(
    tag="div",
    classes=["container"],
    childs=[
        pw.Element(tag="p", content="Hello world"),
        pw.Element(tag="button", content="Press"),
    ],
)

modified = original.clone
modified.add_class("main")
modified.childs[0].content = "Updated content"

diff = TemplateDiff()
diff.track_differences(modified, original)

# diff.differences maps uuid → {status, element, parent, ...}

See Reactivity guide for the full update loop.

Example: Clock Component

Here's an example of creating a clock component with nested elements and content placeholders:

import pyweber as pw
from datetime import datetime
import asyncio

class Text(pw.Element):
    def __init__(self, title: str, value: str):
        super().__init__(tag="div")
        self.classes = ['time-element']
        self.childs = [
            pw.Element(tag="p", content=str(value), classes=['time-value']),
            pw.Element(tag="span", content=title)
        ]

class Box(pw.Element):
    def __init__(self, content: str):
        super().__init__(tag="div", classes=["time-box"], content=content)

class Button(pw.Element):
    def __init__(self, value: str, events: pw.TemplateEvents = None):
        super().__init__(
            tag="button", 
            classes=["time-button"],
            value=value,
            events=events
        )

class Title(pw.Element):
    def __init__(self, content: str):
        super().__init__(tag="h2", classes=['time-title'], content=content)

class ClockSpace(pw.Element):
    def __init__(self):
        super().__init__(tag='div', classes=['clock-space'])
        self.childs = [
            Title(content='Clock'),
            pw.Element(
                tag='div',
                classes=['clock-text'],
                childs=[Box(content='00'), Box(content='00'), Box(content='00')]
            ),
            pw.Element(
                tag='div',
                classes=['clock-buttons'],
                childs=[
                    Button(value='start', events=pw.TemplateEvents(onclick=self.start_clock)),
                    Button(value='stop', events=pw.TemplateEvents(onclick=self.stop_clock))
                ]
            )
        ]

    async def start_clock(self, e: pw.EventHandler):
        self.is_running = True
        hour, minutes, seconds = e.template.querySelectorAll('.time-box')
        while self.is_running:
            hour.content, minutes.content, seconds.content = await self.get_hour()
            e.update()
            await asyncio.sleep(1)

    async def stop_clock(self, e: pw.EventHandler):
        self.is_running = not self.is_running
        hour, minutes, seconds = e.template.querySelectorAll('.time-box')
        hour.content, minutes.content, seconds.content = '00:00:00'.split(':')
        e.update()

    async def get_hour(self) -> tuple[str, str, str]:
        return datetime.now().strftime('%H:%M:%S').split(':')

Best Practices

  1. Use Custom Components: Create reusable components by extending Element
  2. Use Form Components: Leverage the built-in form components for proper HTML attributes
  3. Leverage Clone: Use the clone property when you need independent copies
  4. Combine content and children: Use {{uuid}} placeholders — see Element model
  5. Maintain Element Hierarchy: Keep parent-child relationships consistent
  6. Update the UI: Always call e.update() after making changes
  7. Use Semantic HTML: Choose appropriate HTML tags for their intended purpose
  8. Organize Event Handlers: Keep event handlers focused on specific tasks

Advanced Techniques

Dynamic Element Creation

def add_comment(self, e: pw.EventHandler):
    text = self.comment_input.value.strip()
    if text:
        # Create comment with avatar and text
        avatar = pw.Element(
            tag="img",
            classes=["avatar"],
            attrs={"src": "/images/user.png"}
        )

        comment = pw.Element(
            tag="div",
            classes=["comment"]
        )

        # Add avatar as child and use placeholder in content
        comment.childs = [avatar]
        comment.content = f"{{{{{avatar.uuid}}}}} {text}"

        # Add to comments section
        self.comments_section.add_child(comment)

        # Clear input
        self.comment_input.value = ""

        e.update()

Using HTMLTag Enum

PyWeber provides an HTMLTag enum for type safety:

from pyweber import HTMLTag

# Create element using enum
header = pw.Element(
    tag=HTMLTag.h1,
    content="Page Title"
)

# Create a list
list_element = pw.Element(tag=HTMLTag.ul)
for i in range(5):
    item = pw.Element(
        tag=HTMLTag.li,
        content=f"Item {i+1}"
    )
    list_element.add_child(item)

Next Steps