Styling¶
Style properties are passed via the style prop on every element
factory. The value can be a plain dict, a typed
Style TypedDict built with
pn.style(...), a list mixing those
(later entries win on key collision), or None. PythonNative also
provides a StyleSheet utility for
declaring named styles and a theming system via context.
Inline styles¶
Pass a style dict to components:
pn.Text("Hello", style={"color": "#FF3366", "font_size": 24, "bold": True})
pn.Button("Tap", style={"background_color": "#FF1E88E5", "color": "#FFFFFF"})
pn.Column(pn.Text("Content"), style={"background_color": "#FFF5F5F5"})
Typed styles with pn.style()¶
pn.style(**props) is a tiny helper that
returns a pn.Style TypedDict. Values are
plain Python dict instances at runtime, but the type is fully
recognised by static checkers (mypy, pyright, Pylance) and editors
will autocomplete known keys and Literal values:
import pythonnative as pn
heading: pn.Style = pn.style(
font_size=28,
font_weight="700", # Literal: "100".."900" | "bold" | "normal" | …
text_align="center", # Literal: "left" | "center" | "right" | "justify"
color="#0F172A",
)
pn.Text("Welcome", style=heading)
Why use pn.style() over a raw dict?
- IDE autocomplete for every supported key (
flex_direction,align_items,transform,shadow_offset, …). - Type-checked literals — typos like
align_items="centre"are flagged before you ever run the app. - Self-documenting code — the
pn.Styleannotation tells readers this dict is meant to flow into thestyleprop.
Because Style is total=False, every key is optional; you only
include the props you care about. Plain dicts continue to work
everywhere (they're widened to the same StyleProp type) and
existing code does not need to change.
StyleProp for component authors¶
The argument type accepted by every built-in factory is
pn.StyleProp:
Use it in your own components when you want to forward styles through:
from typing import Optional
import pythonnative as pn
@pn.component
def Card(
*children: pn.Element,
style: Optional[pn.StyleProp] = None,
) -> pn.Element:
base: pn.Style = pn.style(
padding=16,
border_radius=12,
background_color="#FFFFFF",
)
return pn.View(*children, style=[base, style])
The list form lets callers layer overrides on top of base without
losing any keys you didn't override.
StyleSheet¶
Create reusable named styles with
StyleSheet.create:
import pythonnative as pn
styles = pn.StyleSheet.create(
title={"font_size": 28, "bold": True, "color": "#333"},
subtitle={"font_size": 14, "color": "#666"},
container={"padding": 16, "spacing": 12, "align_items": "stretch"},
)
pn.Text("Welcome", style=styles["title"])
pn.Column(
pn.Text("Subtitle", style=styles["subtitle"]),
style=styles["container"],
)
Composing styles¶
Merge multiple style dicts with
StyleSheet.compose:
base = {"font_size": 16, "color": "#000"}
highlight = {"color": "#FF0000", "bold": True}
merged = pn.StyleSheet.compose(base, highlight)
# Result: {"font_size": 16, "color": "#FF0000", "bold": True}
Combining styles with a list¶
You can also pass a list of dicts to style. They are merged left-to-right:
Flattening styles¶
Flatten a style or list of styles into a single dict:
StyleSheet.absolute_fill¶
Convenience factory for the common "fill the parent" overlay style:
overlay = pn.StyleSheet.absolute_fill()
# {"position": "absolute", "top": 0, "right": 0, "bottom": 0, "left": 0}
pn.View(pn.Text("Loading…"), style=[overlay, {"background_color": "#0008"}])
Colors¶
Pass hex strings (#RRGGBB or #AARRGGBB) to color properties inside style:
pn.Text("Hello", style={"color": "#FF3366"})
pn.Button("Tap", style={"background_color": "#FF1E88E5", "color": "#FFFFFF"})
Text styling¶
Text accepts the full typography surface inside style:
| Prop | Value | Notes |
|---|---|---|
font_size |
number | In pt (iOS) / sp (Android) |
color |
hex string | #RRGGBB or #AARRGGBB |
bold |
bool | Shorthand for font_weight: "bold" |
font_weight |
"normal", "bold", "100"–"900" |
|
font_family |
string | System font name |
italic |
bool | |
text_align |
"left", "center", "right", "justify" |
|
letter_spacing |
number | Tracking in points |
line_height |
number | Multiple of font size |
text_decoration |
"underline", "line_through", or None |
|
max_lines |
int | Truncate after N lines |
pn.Text(
"Headline",
style={
"font_size": 28,
"font_weight": "700",
"letter_spacing": -0.5,
"line_height": 32,
"color": "#0F172A",
},
)
Borders, shadows, and shape¶
Every element accepts these visual props in style:
| Prop | Value |
|---|---|
border_radius |
number (uniform) |
border_width |
number (in pt / dp) |
border_color |
hex string |
shadow_color |
hex string |
shadow_offset |
{"width": x, "height": y} |
shadow_opacity |
0.0 – 1.0 |
shadow_radius |
number (blur radius) |
elevation |
number (Android Material shadow shorthand) |
opacity |
0.0 – 1.0 |
tint_color |
hex string (Image only) |
pn.View(
pn.Text("Card"),
style={
"padding": 20,
"background_color": "#FFFFFF",
"border_radius": 16,
"border_width": 1,
"border_color": "#E5E7EB",
"shadow_color": "#000000",
"shadow_offset": {"width": 0, "height": 4},
"shadow_opacity": 0.08,
"shadow_radius": 12,
"elevation": 4,
},
)
Transforms¶
transform is either a 6-element CGAffineTransform-style array or a
shorthand mapping:
pn.View(
pn.Text("Tilted"),
style={
"transform": {"rotate": 15, "scale": 1.1, "translate_x": 10},
},
)
Supported keys: rotate (degrees), scale, scale_x, scale_y,
translate_x, translate_y. For animated transforms, see
Animations.
Flex layout¶
PythonNative uses a Yoga-style flexbox layout model implemented in
pure Python (see Layout engine). View is
the universal flex container, and Column/Row are convenience
wrappers that fix the direction.
Flex container properties¶
These go in the style dict of View, Column, or Row:
flex_direction:"column"(default),"row","column_reverse","row_reverse"(only forView;ColumnandRowhave fixed directions).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".spacing: gap between children (dp / pt).padding: inner spacing (int for all sides, or dict).
Child layout properties¶
All components accept these in style:
width,height: fixed dimensions (number in dp / pt, or percentage string like"50%").min_width,min_height,max_width,max_height: size constraints.aspect_ratio: derive the unknown axis from the known one (width / height).flex: shorthand forflex_grow: N, flex_shrink: 1, flex_basis: 0.flex_grow,flex_shrink,flex_basis: explicit flex properties.margin: outer spacing (number for all sides, or dict).align_self: override parent alignment:"auto","flex_start","center","flex_end","stretch".position:"relative"(default) or"absolute".top,right,bottom,left: edge offsets whenposition: "absolute"(number or percentage string).
Layout examples¶
Centering content:
pn.View(
pn.Text("Centered!"),
style={"flex": 1, "justify_content": "center", "align_items": "center"},
)
Horizontal row with spacer:
pn.Row(
pn.Text("Left"),
pn.Spacer(flex=1),
pn.Text("Right"),
style={"padding": 16, "align_items": "center"},
)
Child with flex grow:
pn.Column(
pn.Text("Header", style={"font_size": 20, "bold": True}),
pn.View(pn.Text("Content area"), style={"flex": 1}),
pn.Text("Footer"),
style={"flex": 1, "spacing": 8},
)
Horizontal button bar:
pn.Row(
pn.Button("Cancel", style={"flex": 1}),
pn.Button("OK", style={"flex": 1, "background_color": "#007AFF", "color": "#FFF"}),
style={"spacing": 8, "padding": 16},
)
Absolute positioning:
pn.View(
pn.View(style={"position": "absolute", "top": 0, "left": 0,
"width": 40, "height": 40, "background_color": "#F00"}),
pn.View(style={"position": "absolute", "bottom": 0, "right": 0,
"width": 40, "height": 40, "background_color": "#0A0"}),
pn.Text("Centered overlay", style={
"position": "absolute",
"top": "50%", "left": "10%", "right": "10%",
"text_align": "center",
}),
style={"width": 240, "height": 160, "background_color": "#EEE"},
)
Aspect-ratio thumbnail grid cell:
pn.View(
pn.Image(source="cover.jpg", style={"flex": 1}),
style={"width": "33%", "aspect_ratio": 1.0, "padding": 4},
)
Layout with Column and Row¶
Column (vertical) and Row (horizontal) are convenience wrappers for View:
pn.Column(
pn.Text("Username"),
pn.TextInput(placeholder="Enter username"),
pn.Text("Password"),
pn.TextInput(placeholder="Enter password", secure=True),
pn.Button("Login", on_click=handle_login),
style={"spacing": 8, "padding": 16, "align_items": "stretch"},
)
Alignment properties¶
Column and Row support align_items and justify_content inside
style:
align_items: cross-axis alignment:"stretch","flex_start","center","flex_end","leading","trailing".justify_content: main-axis distribution:"flex_start","center","flex_end","space_between","space_around","space_evenly".
pn.Row(
pn.Text("Left"),
pn.Spacer(flex=1),
pn.Text("Right"),
style={"align_items": "center", "justify_content": "space_between", "padding": 16},
)
Spacing¶
spacingsets the gap between children in dp (Android) / points (iOS).
Padding¶
padding: 16: all sides.padding: {"horizontal": 12, "vertical": 8}: per axis.padding: {"left": 8, "top": 16, "right": 8, "bottom": 16}: per side.
Theming¶
PythonNative includes a built-in theme context with light and dark themes:
import pythonnative as pn
from pythonnative.style import DEFAULT_DARK_THEME
@pn.component
def ThemedText(text: str = ""):
theme = pn.use_context(pn.ThemeContext)
return pn.Text(text, style={"color": theme["text_color"], "font_size": theme["font_size"]})
@pn.component
def DarkPage():
return pn.Provider(pn.ThemeContext, DEFAULT_DARK_THEME,
pn.Column(
ThemedText(text="Dark mode!"),
style={"spacing": 8},
)
)
Theme properties¶
Both light and dark themes include:
primary_color,secondary_color: accent colors.background_color,surface_color: background colors.text_color,text_secondary_color: text colors.error_color,success_color,warning_color: semantic colors.font_size,font_size_small,font_size_large,font_size_title: typography.spacing,spacing_large: layout spacing.border_radius: corner rounding.
ScrollView¶
Wrap content in a ScrollView:
Next steps¶
- See it in practice: Forms, Lists.
- Browse the API: Style, Components.
- Forward typed styles through your own widgets: Custom native components.
- Learn about reconciliation and how style props are diffed: Reconciliation.