Styling and Theming

SkillMedia

PyWry 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.

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