Skip to content

Fields

Every attribute declared with Field() on a grid does two jobs:

  1. Typed data (defaults, validation metadata).
  2. A rectangular slot on the grid — size, style, and whether it paints.

A field can set:

  • Size — width / height as cells, percentages, fr, or "fit"
  • Appearance — foreground, background, border, padding, title, …
  • Whether it paints — state=True holds data and never claims a slot

Declaring a Field

Declaring a Field
from xnano import BaseGrid, Field

class Card(BaseGrid, direction="vertical"):
    heading: str = Field(
        default="Reminder",
        foreground="violet-400",
        border="rounded",
        width="fit",
    ) # (1)!
    body: str = Field(default="Water the plants.") # (2)!
  1. Defaults and style keywords are arguments to the same Field() call.
  2. A field with no style keywords still paints — it uses leftover space and the grid's plain look.

The full keyword list is on the Field API page.

Runnable Example

Interactive

This code block runs in the browser via Pyodide.

Try editing the code!
  • Change foreground on heading (e.g. "emerald-400").
  • Change border to "double" or "plain".

Editor (session: default)Run
from xnano import BaseGrid, Field, render

class Card(BaseGrid, direction="vertical"):
    heading: str = Field(
        default="Reminder",
        foreground="violet-400",
        border="rounded",
        width="fit",
    )
    body: str = Field(default="Water the plants.")

render(Card())
OutputClear

State Fields

state=True marks data that should not render:

State Field
count: int = Field(default=0, state=True)

Assigning to a state field still schedules a repaint of the grid that owns it. Shared application state (Terminal(state=...) / Web(state=...)), focus group=, and keeping painted fields in sync are covered under State.

Fields vs. Plain Attributes

Not every attribute needs Field(). A plain annotation is a normal Python attribute: typed, live, never painted.

Interactive

This code block runs in the browser via Pyodide.

Fields vs. Plain Attributes
from xnano import BaseGrid, Field

class Card(BaseGrid, direction="vertical"):
    heading: str = Field(default="Reminder")
    tags: list[str] = ["home", "chores"] # (1)!
  1. tags is never given a slot, so it never appears on the grid.

state=True still goes through Field() metadata and validation. Use a plain attribute when you do not need that.

Runnable Example

Try editing the code!
  • Change heading's default=.
  • Add another entry to the tags list (it still will not paint).

Editor (session: default)Run
from xnano import BaseGrid, Field, render

class Card(BaseGrid, direction="vertical"):
    heading: str = Field(default="Reminder")
    # this is a plain attribute, it will not render
    tags: list[str] = ["home", "chores"]

render(Card())
OutputClear

Borders, focus, and overlays

Any painted field can take border chrome without a wrapper grid:

notes: str = Field(
    default="…",
    border="rounded",
    title="Saved Notes",
    padding=1,
)
  • group= names a focus target for ctx.focus(...) / ctx.runtime.focus(...) and click/focus hooks.
  • overlay=True floats the field over the grid content (centered; use z and visible as needed).
  • width / height accept absolute cells, "50%", "1fr", "fit", and similar sizing strings.

See Getting Started for group and overlay popups.

Refreshing fields each frame

Override grid_render on the grid to sync painted fields from state before layout:

grid_render
from xnano import BaseGrid, Context, Field

class Dashboard(BaseGrid):
    stats: str = Field(default="")

    def grid_render(self, ctx: Context) -> None:
        count = len(ctx.state.saved) if ctx.state is not None else 0
        self.stats = f"{count} notes"

Change layout metadata at runtime with grid_set_field (not for state=True fields):

grid_set_field
self.grid_set_field("editor", visible=True, z=2)

Optional responsive hooks (grid_render_small, grid_render_medium, …) run when the viewport crosses size tiers — see BaseGrid.

API

Field · BaseGrid · Styling