Fields¶
Every attribute declared with Field() on a grid does two jobs:
- Typed data (defaults, validation metadata).
- A rectangular slot on the grid — size, style, and whether it paints.
A field can set:
- Size —
width/heightas cells, percentages,fr, or"fit" - Appearance —
foreground,background,border,padding,title, … - Whether it paints —
state=Trueholds data and never claims a slot
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)!
- Defaults and style keywords are arguments to the same
Field()call. - 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
foregroundonheading(e.g."emerald-400"). - Change
borderto"double"or"plain".
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())
State Fields¶
state=True marks data that should not render:
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.
from xnano import BaseGrid, Field
class Card(BaseGrid, direction="vertical"):
heading: str = Field(default="Reminder")
tags: list[str] = ["home", "chores"] # (1)!
tagsis 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'sdefault=. - Add another entry to the
tagslist (it still will not paint).
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())
Borders, focus, and overlays¶
Any painted field can take border chrome without a wrapper grid:
group=names a focus target forctx.focus(...)/ctx.runtime.focus(...)and click/focus hooks.overlay=Truefloats the field over the grid content (centered; usezandvisibleas needed).width/heightaccept 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:
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):
Optional responsive hooks (grid_render_small, grid_render_medium, …) run when
the viewport crosses size tiers — see
BaseGrid.