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¶
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.
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¶
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.