Styling and themes
Styles are dicts with React Native keys. Themed style sheets follow light and dark mode, safe area insets and breakpoints by themselves, with no hook and no re-render.
StyleSheet
StyleSheet.create takes a dict of named styles, or keyword arguments. pyxpo checks the sheet when it is created: a bad value, such as a color it cannot read, raises StyleError, and an unknown key prints a warning.
from pyxpo import StyleSheet, Text, View
styles = StyleSheet.create({
"card": {"padding": 16, "borderRadius": 16, "backgroundColor": "#fff"},
"title": {"fontSize": 18, "fontWeight": "700"},
})
def Card(title):
return View(Text(title, style=styles.title), style=[styles.card, {"marginTop": 8}])- Keys work in camelCase (
backgroundColor) and snake_case (background_color). - A list of styles merges from left to right.
FalseandNoneentries are skipped, so[styles.row, active and styles.active]works. - Flexbox works as in React Native:
flexDirection,gap,flexWrap,alignContent, percent sizes and more, on every target.
Themes
The theme API follows react-native-unistyles 3. Register your themes once, then give StyleSheet.create a function of theme and rt. Components only use styles.name.
from pyxpo import StyleSheet
LIGHT = {"colors": {"bg": "#F4F5FB", "card": "#FFFFFF", "text": "#12142B", "accent": "#6C5CE7"}}
DARK = {"colors": {"bg": "#0C0D1A", "card": "#171933", "text": "#F2F3FF", "accent": "#8B7CFF"}}
StyleSheet.configure(themes={"light": LIGHT, "dark": DARK})from pyxpo import StyleSheet, Text, View, component
import app._theme # registers the themes
styles = StyleSheet.create(lambda theme, rt: {
"screen": {"flex": 1, "backgroundColor": theme.colors.bg, "paddingTop": rt.insets.top},
"card": {"padding": 16, "borderRadius": 16, "backgroundColor": theme.colors.card},
"title": {"fontSize": 20, "fontWeight": "700", "color": theme.colors.text},
})
@component
def Home():
return View(View(Text("Hello", style=styles.title), style=styles.card), style=styles.screen)
default = HomeWith themes named light and dark, the app follows the system setting. When the user switches to dark mode, pyxpo rebuilds the sheet for the dark theme and sends new styles only to the views that use it. Home does not run again.
Themes can hold anything, not only colors: spacing, radii, font sizes. Read them as attributes (theme.space.md) or as keys (theme["space"]["md"]). A missing key raises AttributeError with its name.
Pick a theme in code
StyleSheet.setTheme("dark") # show dark and stop following the system
StyleSheet.setAdaptiveThemes(True) # follow the system again
# More than two themes, or start on one:
StyleSheet.configure(
themes={"light": LIGHT, "dark": DARK, "sepia": SEPIA},
adaptiveThemes=False,
initialTheme="sepia",
)The rt argument
rt holds device values that styles often need. When one of them changes, the styles that read it update.
| Field | Value |
|---|---|
rt.insets | Safe area insets in dp: rt.insets.top, .right, .bottom, .left. |
rt.breakpoint | The active breakpoint, such as "md". |
rt.color_scheme | The system setting: "light" or "dark". |
rt.theme_name | The name of the theme on screen. |
rt.font_scale | The text size the user picked in the system settings. |
Dynamic styles
A style that depends on a value from the component is a function entry. Call it with the value. Each set of arguments is checked once and cached.
styles = StyleSheet.create(lambda theme, rt: {
"avatar": lambda size: {"width": size, "height": size, "borderRadius": size / 2,
"backgroundColor": theme.colors.accent},
})
View(style=styles.avatar(48))Function entries work in a plain StyleSheet.create({...}) as well.
Breakpoint values
Any style value can be a breakpoint map. It takes the value of the largest breakpoint at or below the current window width, mobile first. See Responsive layout for the full guide.
styles = StyleSheet.create(lambda theme, rt: {
"title": {"fontSize": {"base": 20, "lg": 28}, "color": theme.colors.text},
"list": {"flexDirection": {"base": "column", "md": "row"}, "gap": 12},
})useAppTheme
Styles update without any hook. For values that are not styles, such as an icon color, read the theme with useAppTheme(). The component re-renders when the theme changes.
from pyxpo import Pressable, component, useAppTheme
from pyxpo.vector_icons import Feather
from pyxpo.router import router
@component
def BackButton():
theme = useAppTheme()
return Pressable(Feather(name="arrow-left", size=22, color=theme.colors.text),
onPress=router.back, style=styles.back)useTheme in pyxpo.router is the navigation theme (header and tab bar colors). The app theme from StyleSheet.configure is useAppTheme.
Themed styles are dicts
styles.card is a dict with the values of the current theme. Lists, {**styles.card} and StyleSheet.flatten work as with any style.
Reference
| Call | What it does |
|---|---|
StyleSheet.configure(themes, breakpoints, adaptive_themes, initial_theme) | Register the themes once, before the app renders. camelCase keywords (adaptiveThemes, initialTheme) work too. Calling it again replaces the themes. |
StyleSheet.create(lambda theme, rt: {...}) | A themed sheet. lambda theme: {...} works when the sheet does not need rt. |
StyleSheet.setTheme(name) | Show a theme and stop following the system. An unknown name raises ValueError. |
StyleSheet.setAdaptiveThemes(on) | Follow the system light / dark setting again, or stop following it. |
useAppTheme() | The current theme in a component. Re-renders the component when the theme changes. |
StyleSheet.flatten(style) | Merge a list of styles into one dict. Works with themed styles. |
Every call has a snake_case name too (set_theme, set_adaptive_themes, use_app_theme). The same functions live in pyxpo.themes. A complete app that uses all of this is examples/imagekit in the pyxpo repository.