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

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:

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.

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

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:

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