Skip to content

Components

PythonNative uses a declarative component model inspired by React. You describe what the UI should look like, and the framework handles creating and updating native views.

Element functions

UI is built with element-creating functions. Each returns a lightweight Element descriptor; no native objects are created until the Reconciler mounts the tree.

import pythonnative as pn

pn.Text("Hello", style={"font_size": 18, "color": "#333333"})
pn.Button("Tap me", on_press=lambda: print("tapped"))
pn.Column(
    pn.Text("First"),
    pn.Text("Second"),
    style={"gap": 8, "padding": 16},
)

Available components

Layout:

Display:

Input:

Feedback:

Overlay:

Error handling:

Composition:

  • Fragment(*children, key): group siblings into a parent's child list without an extra wrapping view (analogous to React's <>…</>). A keyed Fragment moves all of its children as one unit during keyed reconciliation.
  • Components may also return a plain list of elements, or None to render nothing; None and False children are dropped, so cond and pn.Text(...) works for conditional rendering.

Lists:

Platform UI:

Imperative APIs:

Animations:

  • Animated.View / Animated.Text / Animated.Image: components whose style accepts AnimatedValue instances. Drive animations with Animated.timing, Animated.spring, or Animated.decay. See the Animations guide.

Flex layout model

PythonNative uses a flexbox-inspired layout model. View is the universal flex container; Column and Row are convenience wrappers that fix the direction.

Flex container properties (inside style)

  • flex_direction: "column" (default), "row", "column_reverse", "row_reverse".
  • justify_content: main-axis distribution: "flex_start", "center", "flex_end", "space_between", "space_around", "space_evenly".
  • align_items: cross-axis alignment: "stretch", "flex_start", "center", "flex_end".
  • overflow: "visible" (default), "hidden".
  • gap: space between children (dp / pt).
  • padding: inner spacing.

Child layout properties

All components accept these in their style dict:

  • width, height: fixed dimensions (dp / pt).
  • flex: flex grow factor (shorthand).
  • flex_grow, flex_shrink: individual flex properties.
  • margin: outer margin (int, float, or dict like padding).
  • min_width, min_height: minimum size constraints.
  • max_width, max_height: maximum size constraints.
  • align_self: override parent alignment for this child.

Example: centering content

pn.View(
    pn.Text("Centered"),
    style={"flex": 1, "justify_content": "center", "align_items": "center"},
)

Example: horizontal row with spacing

pn.Row(
    pn.Button("Cancel"),
    pn.Spacer(flex=1),
    pn.Button("OK"),
    style={"padding": 16, "align_items": "center"},
)

Function components: the building block

All UI in PythonNative is built with @pn.component function components. Each screen is a function component that returns an element tree:

@pn.component
def App() -> pn.Node:
    name, set_name = pn.use_state("World")
    return pn.Text(f"Hello, {name}!", style={"font_size": 24})

The entry point create_screen is called internally by native templates to bootstrap your root component. You don't call it directly: name your top-level component App (so the templates can find it by convention) and app.entry_point in pythonnative.toml points at the module that defines it.

State and re-rendering

Use use_state(initial) to create local component state. Call the setter to update; the framework automatically re-renders the component and applies only the differences to the native views:

@pn.component
def CounterPage() -> pn.Node:
    count, set_count = pn.use_state(0)

    return pn.Column(
        pn.Text(f"Count: {count}", style={"font_size": 24}),
        pn.Button("Increment", on_press=lambda: set_count(count + 1)),
        style={"gap": 12},
    )

Composing components

Build complex UIs by composing smaller @pn.component functions. Each instance has independent state:

@pn.component
def Counter(label: str = "Count", initial: int = 0) -> pn.Node:
    count, set_count = pn.use_state(initial)

    return pn.Column(
        pn.Text(f"{label}: {count}", style={"font_size": 18}),
        pn.Row(
            pn.Button("-", on_press=lambda: set_count(count - 1)),
            pn.Button("+", on_press=lambda: set_count(count + 1)),
            style={"gap": 8},
        ),
        style={"gap": 4},
    )


@pn.component
def App() -> pn.Node:
    return pn.Column(
        Counter(label="Apples", initial=0),
        Counter(label="Oranges", initial=5),
        style={"gap": 16, "padding": 16},
    )

Changing one Counter doesn't affect the other; each has its own hook state.

Children

Children are positional, for your components exactly as for the built-in containers. A component that accepts children declares *children: pn.Node:

@pn.component
def Card(*children: pn.Node, title: str) -> pn.Node:
    return pn.Column(
        pn.Text(title, style={"bold": True}),
        *children,
        style={"padding": 12, "border_radius": 8},
    )


Card(pn.Text("Body"), pn.Button("OK"), title="Hello")

pn.Node is the type of anything that can appear in a tree: an element, None or a bool for "nothing", or a (possibly nested) iterable of nodes. That makes the usual conditional idioms type-check:

pn.Column(
    pn.Text(status) if status else None,
    is_admin and AdminBanner(),
    (Row(item=item).with_key(item.id) for item in items),
)

None and False are dropped during reconciliation, and nested iterables are flattened. Strings aren't nodes: wrap text in pn.Text. Annotate every component's return type as pn.Node, since a component may return an element, a list of siblings, or None to render nothing.

Typed props

@pn.component preserves the function's signature for type checkers (it's a ParamSpec), so Card(titel="x") is a static error and editors autocomplete props from the function definition.

In development builds (pn start, pn run without --release, and every test run under the pytest plugin), each call is also checked against the function's annotations at run time. A mismatch, such as Card(title=3) from untyped code, produces one diagnostics warning per component and parameter instead of an exception. The checker understands str, int, float (which accepts int), bool, None, Optional, unions, Literal, Sequence, Mapping, Callable, dataclasses, and plain classes, and skips annotations it can't evaluate. Release builds skip the check entirely, along with the built-in components' prop and style validation. Static checking remains the primary guard.

Keys

A key identifies an element among its siblings so the reconciler can match it across renders when a list is reordered, inserted into, or filtered. Built-in factories take key= directly. User components don't: key isn't a prop, and PEP 612 doesn't allow a typed keyword next to a component's own signature. Key any element with .with_key() instead:

@pn.component
def Row(item: Item) -> pn.Node:
    return pn.Text(item.title)


pn.Column(*(Row(item).with_key(item.id) for item in items))
pn.Column(*(pn.Text(item.title, key=str(item.id)) for item in items))

with_key accepts any value and converts non-strings with str. Passing key= to a user component raises TypeError pointing to .with_key(), a component may not declare a parameter named key, and pn lint reports key= on a component call (rule PN104) before you run the app.

Available hooks

Custom hooks

Extract reusable stateful logic into plain functions:

from collections.abc import Callable


def use_toggle(initial: bool = False) -> tuple[bool, Callable[[], None]]:
    value, set_value = pn.use_state(initial)

    def toggle() -> None:
        set_value(lambda current: not current)

    return value, toggle

A custom hook is a function whose name starts with use_. It follows the same rules as the built-in hooks, which pn lint checks; see Hooks.

Context and Provider

Share values across the tree without prop drilling:

Accent = pn.create_context("#007AFF", name="Accent")


@pn.component
def App() -> pn.Node:
    return Accent.Provider(MyComponent(), value="#FF2D55")


@pn.component
def MyComponent() -> pn.Node:
    accent = pn.use_context(Accent)
    return pn.Button("Click", style={"color": accent})

For colors and other design tokens, use the built-in Theme and ThemeProvider rather than a context of your own; see Styling. For app state that many components read, see Managing state.

Platform detection

The recommended way to write platform-aware code is via Platform:

import pythonnative as pn

title = pn.Platform.select({"ios": "iOS App", "android": "Android App"})

if pn.Platform.is_ios:
    margin = 16

pn.Platform.OS is "ios", "android", "web" (the pn preview browser preview, see the Browser preview guide), or "test" (off-device, e.g. in unit tests). The lower-level utils.IS_ANDROID / utils.IS_IOS / utils.IS_WEB constants are still available.

Platform.select matches on the exact key; a "native" key is shared by iOS and Android (but not the browser preview), and a "default" key catches anything unmatched:

pad = pn.Platform.select({"native": 16, "web": 12, "default": 8})

Next steps