Styling and Theming
SkillMediaPyWry provides CSS variables that adapt to dark/light themes. Use these instead of hardcoded colors!
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Styling and Theming skill
About this capability
PyWry is a cross-platform app factory, rendering engine and UI toolkit for Python that produces native desktop, web, and notebook experiences from a single API.
What this skill tells your AI
The instructions your AI receives, as published by deeleeramone/pywry in pywry/pywry/mcp/skills/styling/SKILL.md and read by ahel’s review.
Style widgets using CSS variables, theme colors, and dynamic style updates.
Theme CSS Variables
PyWry provides CSS variables that adapt to dark/light themes. Use these instead of hardcoded colors!
Text Colors
var(--text-primary) /* Main text - high contrast */
var(--text-secondary) /* Muted text - medium contrast */
var(--text-muted) /* Very subtle text - low contrast */
Background Colors
var(--bg-primary) /* Main background (page/window) */
var(--bg-secondary) /* Card/section background */
var(--bg-tertiary) /* Nested elements, insets */
var(--bg-hover) /* Hover states */
Border Colors
var(--border-color) /* Standard borders */
var(--border-subtle) /* Subtle dividers */
Accent Colors
var(--accent-color) /* Primary accent (brand color) */
var(--accent-hover) /* Accent hover state */
Status Colors
var(--success-color) /* Green - positive, success */
var(--warning-color) /* Orange/Yellow - caution */
var(--error-color) /* Red - error, danger */
var(--info-color) /* Blue - informational */
Using Theme Variables
In Div Component
content = Div(
content="Status: OK",
component_id="status",
style="color: var(--success-color); background: var(--bg-secondary); padding: 10px;",
)
In HTML Content
html = """
<div style="
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 16px;
">
<h2 style="color: var(--text-primary); margin: 0;">Title</h2>
<p style="color: var(--text-secondary);">Description</p>
</div>
"""
Via inject_css
inject_css(widget_id, css="""
.custom-card {
background: var(--bg-secondary);
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.custom-card .title {
color: var(--text-primary);
font-weight: 600;
}
.custom-card .subtitle {
color: var(--text-muted);
font-size: 0.875rem;
}
""", id="custom-styles")
Dynamic Style Updates
Using set_style
# Update styles on an element by component_id
set_style(widget_id, component_id="status", styles={
"color": "var(--error-color)",
"backgroundColor": "var(--bg-tertiary)",
"borderLeft": "4px solid var(--error-color)",
})
# Update by selector
set_style(widget_id, selector=".price.up", styles={
"color": "var(--success-color)",
})
set_style(widget_id, selector=".price.down", styles={
"color": "var(--error-color)",
})
Style Property Names
Both camelCase and kebab-case work:
# camelCase (JavaScript style)
set_style(widget_id, component_id="box", styles={
"backgroundColor": "red",
"fontSize": "16px",
"borderRadius": "8px",
})
# kebab-case (CSS style)
set_style(widget_id, component_id="box", styles={
"background-color": "red",
"font-size": "16px",
"border-radius": "8px",
})
Theme Switching
Programmatic Switching
# Switch to dark theme
update_theme(widget_id, theme="dark")
# Switch to light theme
update_theme(widget_id, theme="light")
# Follow OS preference (recommended)
update_theme(widget_id, theme="system")
Theme Detection
# Let the widget follow system preference
update_theme(widget_id, theme="system")
# The CSS variables automatically update when theme changes
# Your content adapts without code changes
Injecting Custom CSS
Adding Styles
inject_css(widget_id, css="""
/* Custom component styles */
.my-button {
background: var(--accent-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
}
.my-button:hover {
background: var(--accent-hover);
}
/* Status indicators */
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
}
.status-badge.success {
background: var(--success-color);
color: white;
}
.status-badge.error {
background: var(--error-color);
color: white;
}
""", id="custom-styles") # ID lets you remove/replace later
Removing Styles
# Remove previously injected styles
remove_css(widget_id, id="custom-styles")
Replacing Styles
# Inject with same ID replaces previous
inject_css(widget_id, css="...", id="custom-styles") # Replaces previous
Component Styling
Button Variants
Button(label="Primary", variant="primary") # Accent color background
Button(label="Default", variant="default") # Neutral background
Button(label="Ghost", variant="ghost") # Transparent, border only
Button(label="Danger", variant="danger") # Red/error color
Div with Full CSS Control
Div(
content="Card content",
component_id="my-card",
style="padding: 20px; border-radius: 8px; background: var(--bg-secondary);",
class_name="my-card", # For CSS targeting
)
Styling Built-in Components
# Most components accept style and class_name
Select(
label="Choose",
options=[...],
style="width: 200px;",
class_name="compact-select",
)
Responsive Patterns
Media Queries
inject_css(widget_id, css="""
/* Mobile-first base styles */
.dashboard {
display: flex;
flex-direction: column;
gap: 16px;
}
/* Tablet and up */
@media (min-width: 600px) {
.dashboard {
flex-direction: row;
}
.sidebar {
width: 250px;
flex-shrink: 0;
}
.main-content {
flex-grow: 1;
}
}
/* Desktop */
@media (min-width: 1024px) {
.sidebar {
width: 300px;
}
}
""")
Hide/Show Based on Screen Size
inject_css(widget_id, css="""
/* Hide sidebar on mobile */
@media (max-width: 599px) {
.sidebar {
display: none;
}
.main-content {
width: 100%;
}
}
""")
Common Patterns
Card Component
inject_css(widget_id, css="""
.card {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.card-title {
font-size: 1.125rem;
font-weight: 600;
color: var(--text-primary);
}
.card-body {
color: var(--text-secondary);
}
""")
Status Indicators
inject_css(widget_id, css="""
.status-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
}
.status-dot.online { background: var(--success-color); }
.status-dot.offline { background: var(--error-color); }
.status-dot.pending { background: var(--warning-color); }
""")
Price Styling
inject_css(widget_id, css="""
.price {
font-family: monospace;
font-weight: 500;
}
.price.up {
color: var(--success-color);
}
.price.up::before {
content: "▲ ";
}
.price.down {
color: var(--error-color);
}
.price.down::before {
content: "▼ ";
}
""")
Signals
- GitHub stars
- 93
- Forks
- 6
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
styling- Source
- github.com/deeleeramone/pywry