CSS Reference¶
PyWry's styling system is driven by CSS custom properties (variables) with dark and light theme support. The CSS is organized across four source files, each covering a distinct feature area.
Source Files¶
| File | Description | Page |
|---|---|---|
pywry.css |
Core layout, toolbar, buttons, inputs, controls, modal, scrollbars, utilities | Core Stylesheet |
chat.css |
Chat UI — messages, threads, tool calls, artifacts, syntax highlighting | Chat Stylesheet |
toast.css |
Toast notifications — types, positions, blocking overlay | Toast Stylesheet |
tvchart.css |
TradingView chart UI — header, legend, drawing tools, settings panels | TradingView Stylesheet |
CSS Variables¶
All component styles are driven by CSS custom properties. Override them to customize the entire look.
Shared Variables (theme-independent)¶
:root {
/* Typography */
--pywry-font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--pywry-font-size: 14px;
--pywry-font-weight-normal: 400;
--pywry-font-weight-medium: 500;
--pywry-font-mono: 'Cascadia Code', 'Fira Code', Consolas, monospace;
/* Spacing & Radius */
--pywry-radius: 4px;
--pywry-radius-lg: 6px;
--pywry-spacing-xs: 2px;
--pywry-spacing-sm: 4px;
--pywry-spacing-md: 6px;
--pywry-spacing-lg: 8px;
/* Widget Sizing */
--pywry-widget-width: 100%;
--pywry-widget-min-height: 200px;
--pywry-widget-height: 500px;
--pywry-grid-min-height: 200px;
/* Transitions */
--pywry-transition-fast: 0.1s ease;
--pywry-transition-normal: 0.2s ease;
/* Accent Colors */
--pywry-accent: #0078d4;
--pywry-accent-hover: #106ebe;
--pywry-text-accent: rgb(51, 187, 255);
--pywry-btn-neutral-bg: rgb(0, 136, 204);
--pywry-btn-neutral-text: #ffffff;
--pywry-btn-neutral-hover: rgb(0, 115, 173);
/* Scrollbar */
--pywry-scrollbar-size: 10px;
--pywry-scrollbar-thumb: rgba(155, 155, 155, 0.5);
--pywry-scrollbar-thumb-hover: rgba(175, 175, 175, 0.7);
/* Marquee */
--pywry-marquee-speed: 15s;
/* Toast */
--pywry-toast-bg: rgba(30, 30, 30, 0.95);
--pywry-toast-color: #ffffff;
--pywry-toast-accent: #0ea5e9;
/* Modal */
--pywry-modal-overlay-opacity: 0.5;
}
Dark Theme¶
:root, html.dark, .pywry-theme-dark {
--pywry-bg-primary: #212124;
--pywry-bg-secondary: rgba(21, 21, 24, 1);
--pywry-bg-tertiary: rgba(31, 30, 35, 1);
--pywry-bg-quartary: rgba(36, 36, 42, 1);
--pywry-bg-hover: rgba(255, 255, 255, 0.08);
--pywry-bg-overlay: rgba(30, 30, 30, 0.8);
--pywry-text-primary: #ebebed;
--pywry-text-secondary: #a0a0a0;
--pywry-text-muted: #707070;
--pywry-border-color: #333;
--pywry-border-focus: #555;
--pywry-tab-bg: #2a2a2e;
--pywry-tab-active-bg: #3d3d42;
--pywry-tab-hover-bg: #353538;
--pywry-btn-primary-bg: #e2e2e2;
--pywry-btn-primary-text: #151518;
--pywry-btn-primary-hover: #cccccc;
--pywry-btn-secondary-bg: #3d3d42;
--pywry-btn-secondary-text: #ebebed;
--pywry-btn-secondary-hover: #4a4a50;
--pywry-btn-secondary-border: rgba(90, 90, 100, 0.5);
}
Light Theme¶
html.light, .pywry-theme-light {
--pywry-bg-primary: #f5f5f5;
--pywry-bg-secondary: #ffffff;
--pywry-bg-hover: rgba(0, 0, 0, 0.06);
--pywry-bg-overlay: rgba(255, 255, 255, 0.8);
--pywry-text-primary: #000000;
--pywry-text-secondary: #666666;
--pywry-text-muted: #999999;
--pywry-border-color: #ccc;
--pywry-border-focus: #999;
--pywry-tab-bg: #e8e8ec;
--pywry-tab-active-bg: #ffffff;
--pywry-tab-hover-bg: #f0f0f4;
--pywry-btn-primary-bg: #2c2c32;
--pywry-btn-primary-text: #ffffff;
--pywry-btn-primary-hover: #1a1a1e;
--pywry-btn-secondary-bg: #d0d0d8;
--pywry-btn-secondary-text: #2c2c32;
--pywry-btn-secondary-hover: #c0c0c8;
--pywry-btn-secondary-border: rgba(180, 180, 190, 1);
}
System Theme¶
When mode="system", PyWry uses dark by default and applies light overrides via @media (prefers-color-scheme: light).
Custom CSS Injection¶
From Python¶
# Inject CSS at runtime
handle.emit("pywry:inject-css", {
"id": "my-custom-styles",
"css": """
.my-class {
color: var(--pywry-text-accent);
background: var(--pywry-bg-secondary);
}
"""
})
# Remove injected CSS
handle.emit("pywry:remove-css", {"id": "my-custom-styles"})
From JavaScript¶
window.pywry.injectCSS(".highlight { color: red; }", "my-highlights");
window.pywry.removeCSS("my-highlights");
Inline Styles via Event¶
# By element ID
handle.emit("pywry:set-style", {
"id": "my-element",
"styles": {"fontSize": "24px", "fontWeight": "bold"}
})
# By CSS selector
handle.emit("pywry:set-style", {
"selector": ".my-class",
"styles": {"display": "none"}
})
Via HtmlContent¶
from pywry import HtmlContent
content = HtmlContent(
html="<div id='app'></div>",
inline_css="body { font-size: 16px; }",
css_files=["styles/main.css", "styles/theme.css"],
)
Via Configuration¶
# pywry.toml
[theme]
css_file = "styles/custom.css"
[asset]
css_files = ["extra1.css", "extra2.css"]
Override Examples¶
Widget Height¶
app = PyWry(
html="<h1>Tall Widget</h1>",
head='<style>:root { --pywry-widget-height: 800px; }</style>',
)
Custom Theme¶
custom_css = """
<style>
.pywry-theme-custom {
--pywry-bg-primary: #1a1a2e;
--pywry-bg-secondary: #16213e;
--pywry-text-primary: #e94560;
--pywry-accent: #0f3460;
}
</style>
"""
app = PyWry(html=content, head=custom_css)
Custom Button Styles¶
def on_customize(data, event_type, label):
app.emit("pywry:inject-css", {
"id": "custom-buttons",
"css": """
.pywry-btn-primary {
background: linear-gradient(135deg, #667eea, #764ba2);
border: none;
}
.pywry-btn-primary:hover {
background: linear-gradient(135deg, #764ba2, #667eea);
}
"""
}, label)
Data Attributes¶
PyWry uses HTML data attributes for event wiring and component configuration:
| Attribute | Used On | Description |
|---|---|---|
data-event |
Buttons, inputs | Event name to emit (e.g., "app:save") |
data-component-id |
All components, modals | Unique component identifier |
data-data |
Buttons | JSON payload passed with the event |
data-tooltip |
Toolbar items | Tooltip text shown on hover |
data-value |
Dropdown options | Option value |
data-selected |
Dropdown options | Marks the selected option |
data-pywry-chart |
Plotly containers | Chart/Plotly instance identifier |
data-close-escape |
Modals | "true" to close on Escape key |
data-close-overlay |
Modals | "true" to close on overlay click |
data-reset-on-close |
Modals | "true" to reset form fields on close |
data-on-close-event |
Modals | Event emitted when modal closes |
data-collapsible |
Toolbars | "true" for collapsible toolbar |
data-resizable |
Toolbars | "true" for resizable toolbar |
data-accept-types |
Chat input | Accepted file types for upload |