| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [View Raw Code] [Original HTTPS Page] |
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(...)][pythonnative.style.style], a list mixing those (later entries win on key collision), or None. PythonNative also provides a [StyleSheet][pythonnative.StyleSheet] utility for declaring named styles and a theming system via context.
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"})[pn.style(**props)][pythonnative.style.style] is a tiny helper that returns a [pn.Style][pythonnative.style.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?
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.
The argument type accepted by every built-in factory is [pn.StyleProp][pythonnative.style.StyleProp]:
StyleProp = Style | dict[str, Any] | list[Style | dict | None] | NoneUse 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.
Create reusable named styles with [StyleSheet.create][pythonnative.style.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"],
)Merge multiple style dicts with [StyleSheet.compose][pythonnative.style.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}You can also pass a list of dicts to style. They are merged left-to-right:
pn.Text("Highlighted", style=[base, highlight])Flatten a style or list of styles into a single dict:
pn.StyleSheet.flatten([base, highlight])
pn.StyleSheet.flatten(None) # returns {}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"}])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 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 | |
| text_transform | "none", "uppercase", "lowercase", "capitalize" | Applied before measurement |
| max_lines | int | Truncate after N lines |
| text_shadow_color | hex string | |
| text_shadow_offset | {"width": x, "height": y} or (x, y) | |
| text_shadow_radius | number (blur radius) |
pn.Text(
"Headline",
style={
"font_size": 28,
"font_weight": "700",
"letter_spacing": -0.5,
"line_height": 32,
"color": "#0F172A",
},
)text_transform is applied in Python before the string reaches the native label, so the layout engine measures the transformed text and rich-text spans inherit the outer element's transform. "capitalize" upper-cases the first character of each word and leaves the rest as written (it doesn't lower-case like str.title()).
Text shadows render through NSShadow on iOS, TextView.setShadowLayer on Android, and CSS text-shadow in the browser preview:
pn.Text(
"Overlay caption",
style={
"color": "#FFFFFF",
"text_transform": "uppercase",
"text_shadow_color": "#00000099",
"text_shadow_offset": {"width": 0, "height": 1},
"text_shadow_radius": 3,
},
)Text accepts a mix of strings and nested Text elements, which flatten into one native label (a SpannableString on Android, an NSAttributedString on iOS). Each nested Text styles its own run and inherits everything it doesn't override from the outer element:
pn.Text(
"Every plan includes ",
pn.Text("unlimited builds", style={"bold": True}),
" and ",
pn.Text("priority support", style={"color": "#DC2626", "text_decoration": "underline"}),
".",
style={"font_size": 15, "color": "#0F172A"},
)Because the result is a single label, it wraps, truncates (max_lines), and measures as one paragraph; there's no need to assemble rows of separate Text views to mix weights or colors inline.
[Pressable][pythonnative.Pressable]'s style prop also accepts a callable receiving a state dict, mirroring React Native's function-style prop. It's called with {"pressed": bool} and re-applied as the press state changes:
pn.Pressable(
pn.Text("Save"),
on_press=save,
style=lambda state: {
"padding": 12,
"border_radius": 8,
"background_color": "#1D4ED8" if state["pressed"] else "#3B82F6",
},
)Every element accepts these visual props in style:
| Prop | Value |
|---|---|
| border_radius | number (uniform) |
| border_top_left_radius, border_top_right_radius, border_bottom_left_radius, border_bottom_right_radius | number (per corner) |
| border_width | number (in pt / dp) |
| border_color | hex string |
| border_left_width, border_top_width, border_right_width, border_bottom_width | number (per side) |
| border_left_color, border_top_color, border_right_color, border_bottom_color | hex string (per side) |
| 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) |
Per-corner radius props override the uniform border_radius for the corners they name, so a "speech bubble" or "top-rounded sheet" shape needs no images:
pn.View(
content,
style={
"border_top_left_radius": 16,
"border_top_right_radius": 16,
"background_color": "#FFFFFF",
},
)Borders take up space inside the element's frame, exactly like padding: a child inside a border_width: 4 parent starts 4 points in from the parent's edge, and a content-sized parent grows by its border on every side. This matches Yoga's box model, so styles ported from React Native line up without adjustment.
Per-side border props override the uniform border_width / border_color for the sides they name, so an "underline" card is just:
pn.View(
pn.Text("Active tab"),
style={"border_bottom_width": 2, "border_bottom_color": "#007AFF"},
)On Android, shadow_color and shadow_opacity apply on API 28+; older versions fall back to the elevation shadow's default color. When shadow_radius is set without elevation, the elevation is derived from it so shadows show up without extra Android-only props.
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,
},
)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.
PythonNative uses the shared Yoga flexbox engine, compiled into each mobile runtime and provided as WebAssembly in the browser preview (see Layout engine). View is the universal flex container, and Column/Row are convenience wrappers that fix the direction.
These go in the style dict of View, Column, or Row:
align_items: "baseline" (rows only) lines children up along a shared baseline instead of their top edges. Native text handlers report a size but not the position of the first line's baseline, so the engine approximates: a leaf's baseline is its height (leaves align along their bottom edges), and a container's baseline is that of its first in-flow child. Columns treat "baseline" as "flex_start", matching Yoga.
All components accept these in style:
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"},
)Auto margins instead of a spacer:
Auto margins absorb the free space on the main axis before justify_content is applied, following CSS and Yoga. When any child on a line has an auto main-axis margin, the remaining space is split equally among every auto margin on that line and justify_content has no effect. On the cross axis, margin_top: "auto" and friends center or push the child and override align_items / align_self (including stretch):
pn.Row(
pn.Text("Left"),
pn.Text("Right", style={"margin_left": "auto"}), # pushed to the trailing edge
style={"padding": 16},
)
pn.Column(
pn.Text("Centered", style={"margin_horizontal": "auto"}), # centered without align_items
style={"flex": 1},
)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},
)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_press=handle_login),
style={"spacing": 8, "padding": 16, "align_items": "stretch"},
)Column and Row support align_items and justify_content inside style:
pn.Row(
pn.Text("Left"),
pn.Spacer(flex=1),
pn.Text("Right"),
style={"align_items": "center", "justify_content": "space_between", "padding": 16},
)A few props shape how views participate in touch handling and layout measurement.
The pointer_events style key controls whether a view (and its subtree) takes part in hit testing:
pn.View(
style=[pn.StyleSheet.absolute_fill(), {
"background_color": "#00000022",
"pointer_events": "none", # decorative scrim; taps pass through
}],
)hit_slop expands a pressable area beyond the view's visual bounds, so small controls stay comfortably tappable. Pass a number for a uniform expansion or a dict with top / left / bottom / right:
pn.Pressable(
pn.Image(source="close.png", style={"width": 16, "height": 16}),
on_press=dismiss,
hit_slop=12, # 40 x 40 effective target
)View, Column, Row, and Pressable all accept it.
The on_layout prop reports the element's computed frame after each layout pass in which it changed. The payload carries x, y, width, and height in the parent's coordinate space:
def handle_layout(frame):
set_width(frame["width"])
pn.View(content, on_layout=handle_layout)The callback runs post-commit, so setting state inside it is safe and schedules a normal re-render. Use it for measure-then-position patterns (tooltips, anchored popovers) or container-driven item sizing.
[use_color_scheme][pythonnative.use_color_scheme] returns the effective scheme ("light" or "dark") and re-renders the component when it changes, including live when the user flips the system setting while the app is open:
import pythonnative as pn
@pn.component
def Wallpaper():
scheme = pn.use_color_scheme()
bg = "#000000" if scheme == "dark" else "#FFFFFF"
return pn.View(style={"flex": 1, "background_color": bg})[use_theme][pythonnative.use_theme] goes one step further: without any provider it resolves the built-in [DEFAULT_LIGHT_THEME][pythonnative.style.DEFAULT_LIGHT_THEME] or [DEFAULT_DARK_THEME][pythonnative.style.DEFAULT_DARK_THEME] from the current scheme, so themed components are dark-mode aware by default. Themes are typed [Theme][pythonnative.Theme] records, so theme.text_color autocompletes and a typo is a static error:
@pn.component
def ThemedText(text: str = ""):
theme = pn.use_theme()
return pn.Text(text, style={"color": theme.text_color, "font_size": theme.font_size})An in-app appearance toggle overrides the system setting through the appearance module:
pn.appearance.set_color_scheme("dark") # force dark everywhere
pn.appearance.set_color_scheme(None) # follow the system againDerive a brand theme from a built-in one with [Theme.replace][pythonnative.style.Theme.replace], then pin it for a subtree (ignoring the color scheme) with a ThemeContext provider. use_theme returns the provided value as-is, and rejects anything that isn't a Theme with a TypeError:
import pythonnative as pn
BRAND = pn.DEFAULT_DARK_THEME.replace(primary_color="#FF2D55", border_radius=12)
@pn.component
def DarkPage():
return pn.ThemeContext.Provider(
BRAND,
pn.Column(
ThemedText(text="Always dark!"),
style={"spacing": 8},
),
)Every Theme has these fields:
Wrap content in a [ScrollView][pythonnative.ScrollView]:
pn.ScrollView(
pn.Column(
pn.Text("Item 1"),
pn.Text("Item 2"),
style={"spacing": 8},
)
)| Back | FazBrowse Home | New Git URL |