396 lines
7.7 KiB
Markdown
396 lines
7.7 KiB
Markdown
# Using brand.yml with Shiny for Python
|
|
|
|
Guide for applying brand.yml styling to Shiny for Python applications using ui.Theme.
|
|
|
|
## Overview
|
|
|
|
Shiny for Python integrates brand.yml through the `ui.Theme.from_brand()` method, which creates custom themes from `_brand.yml` files. This enables consistent branding across Shiny apps with minimal configuration.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
# Install Shiny with theme support
|
|
pip install "shiny[theme]"
|
|
|
|
# Or install separately
|
|
pip install shiny libsass
|
|
|
|
# Optional: Install brand_yml for programmatic access
|
|
pip install brand_yml
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
### Automatic Discovery
|
|
|
|
Place `_brand.yml` at your app directory root:
|
|
|
|
```
|
|
my-app/
|
|
├── _brand.yml
|
|
├── app.py
|
|
└── ...
|
|
```
|
|
|
|
Then use `ui.Theme.from_brand()`:
|
|
|
|
**Shiny Express:**
|
|
|
|
```python
|
|
from shiny.express import ui
|
|
|
|
ui.page_opts(theme=ui.Theme.from_brand(__file__))
|
|
|
|
# ... rest of app
|
|
```
|
|
|
|
**Shiny Core:**
|
|
|
|
```python
|
|
from shiny import App, ui
|
|
|
|
app_ui = ui.page_fluid(
|
|
ui.Theme.from_brand(__file__),
|
|
ui.h2("My App"),
|
|
# ... rest of UI
|
|
)
|
|
|
|
def server(input, output, session):
|
|
pass
|
|
|
|
app = App(app_ui, server)
|
|
```
|
|
|
|
## ui.Theme.from_brand() Parameters
|
|
|
|
```python
|
|
ui.Theme.from_brand(brand)
|
|
```
|
|
|
|
The `brand` parameter accepts:
|
|
|
|
### File Path (Most Common)
|
|
|
|
```python
|
|
# Use __file__ for app directory
|
|
ui.Theme.from_brand(__file__)
|
|
|
|
# Explicit file path
|
|
ui.Theme.from_brand("path/to/_brand.yml")
|
|
|
|
# Explicit directory (auto-finds _brand.yml)
|
|
ui.Theme.from_brand("branding/")
|
|
```
|
|
|
|
### Brand Object
|
|
|
|
```python
|
|
from brand_yml import Brand
|
|
|
|
brand = Brand.from_yaml("_brand.yml")
|
|
ui.Theme.from_brand(brand)
|
|
```
|
|
|
|
## Search Behavior
|
|
|
|
When given `__file__` or a directory path, the method searches for `_brand.yml`:
|
|
|
|
1. In the specified directory
|
|
2. In `_brand/` subdirectory
|
|
3. In `brand/` subdirectory
|
|
4. In parent directories (recursive)
|
|
|
|
## Complete Examples
|
|
|
|
### Shiny Express App
|
|
|
|
```python
|
|
from shiny.express import input, render, ui
|
|
|
|
ui.page_opts(
|
|
title="My Dashboard",
|
|
theme=ui.Theme.from_brand(__file__)
|
|
)
|
|
|
|
with ui.sidebar():
|
|
ui.input_slider("n", "Number of observations", 1, 100, 50)
|
|
|
|
@render.plot
|
|
def histogram():
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
|
|
data = np.random.randn(input.n())
|
|
plt.hist(data, bins=20)
|
|
plt.xlabel("Value")
|
|
plt.ylabel("Frequency")
|
|
```
|
|
|
|
### Shiny Core App
|
|
|
|
```python
|
|
from shiny import App, render, ui
|
|
|
|
app_ui = ui.page_sidebar(
|
|
ui.sidebar(
|
|
ui.input_slider("n", "Number of observations", 1, 100, 50),
|
|
),
|
|
ui.output_plot("histogram"),
|
|
title="My Dashboard",
|
|
theme=ui.Theme.from_brand(__file__)
|
|
)
|
|
|
|
def server(input, output, session):
|
|
@render.plot
|
|
def histogram():
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
|
|
data = np.random.randn(input.n())
|
|
plt.hist(data, bins=20)
|
|
plt.xlabel("Value")
|
|
plt.ylabel("Frequency")
|
|
|
|
app = App(app_ui, server)
|
|
```
|
|
|
|
### With Custom Path
|
|
|
|
```python
|
|
from shiny.express import ui
|
|
|
|
# Shared brand file
|
|
ui.page_opts(theme=ui.Theme.from_brand("../shared-branding/_brand.yml"))
|
|
|
|
# Named brand file
|
|
ui.page_opts(theme=ui.Theme.from_brand("company-brand.yml"))
|
|
|
|
# Directory with _brand.yml inside
|
|
ui.page_opts(theme=ui.Theme.from_brand("branding/"))
|
|
```
|
|
|
|
### Multiple Page Types
|
|
|
|
```python
|
|
from shiny import App, ui
|
|
|
|
# page_fluid
|
|
app_ui = ui.page_fluid(
|
|
theme=ui.Theme.from_brand(__file__),
|
|
# ... content
|
|
)
|
|
|
|
# page_sidebar
|
|
app_ui = ui.page_sidebar(
|
|
theme=ui.Theme.from_brand(__file__),
|
|
ui.sidebar(
|
|
# ... sidebar content
|
|
),
|
|
# ... main content
|
|
)
|
|
|
|
# page_navbar
|
|
app_ui = ui.page_navbar(
|
|
ui.nav_panel("Tab 1", # ...),
|
|
ui.nav_panel("Tab 2", # ...),
|
|
title="My App",
|
|
theme=ui.Theme.from_brand(__file__)
|
|
)
|
|
|
|
# page_fillable
|
|
app_ui = ui.page_fillable(
|
|
theme=ui.Theme.from_brand(__file__),
|
|
# ... content
|
|
)
|
|
```
|
|
|
|
## Combining with Custom Theme Rules
|
|
|
|
Extend brand.yml themes with custom Sass:
|
|
|
|
```python
|
|
from shiny.express import ui
|
|
|
|
theme = (
|
|
ui.Theme.from_brand(__file__)
|
|
.add_rules("""
|
|
.custom-card {
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
""")
|
|
)
|
|
|
|
ui.page_opts(theme=theme)
|
|
```
|
|
|
|
Available theme methods (chainable):
|
|
- `.add_defaults()` - Override Bootstrap variables
|
|
- `.add_functions()` - Add Sass functions
|
|
- `.add_mixins()` - Add Sass mixins
|
|
- `.add_rules()` - Add CSS rules
|
|
- `.add_uses()` - Add Sass declarations
|
|
|
|
## Programmatic Access with brand_yml
|
|
|
|
For advanced use cases, access brand data programmatically:
|
|
|
|
```python
|
|
from brand_yml import Brand
|
|
|
|
# Read brand file
|
|
brand = Brand.from_yaml("_brand.yml")
|
|
|
|
# Or from string
|
|
yaml_content = """
|
|
color:
|
|
palette:
|
|
blue: "#0066cc"
|
|
primary: blue
|
|
"""
|
|
brand = Brand.from_yaml_str(yaml_content)
|
|
|
|
# Access brand elements
|
|
brand.meta.name # Organization name
|
|
brand.color.palette.blue # "#0066cc"
|
|
brand.color.primary # "blue"
|
|
brand.typography.base.family # Font family name
|
|
|
|
# Use in UI
|
|
from shiny import ui
|
|
|
|
app_ui = ui.page_fluid(
|
|
theme=ui.Theme.from_brand(brand),
|
|
ui.h2(brand.meta.name),
|
|
# ... more content
|
|
)
|
|
```
|
|
|
|
## Sample _brand.yml for Shiny
|
|
|
|
Minimal example:
|
|
|
|
```yaml
|
|
color:
|
|
palette:
|
|
brand-blue: "#0066cc"
|
|
brand-gray: "#666666"
|
|
primary: brand-blue
|
|
foreground: brand-gray
|
|
background: "#ffffff"
|
|
|
|
typography:
|
|
fonts:
|
|
- family: Inter
|
|
source: google
|
|
weight: [400, 600]
|
|
base:
|
|
family: Inter
|
|
size: 16px
|
|
headings:
|
|
family: Inter
|
|
weight: 600
|
|
```
|
|
|
|
More complete example:
|
|
|
|
```yaml
|
|
meta:
|
|
name: My Company
|
|
link: https://mycompany.com
|
|
|
|
color:
|
|
palette:
|
|
blue: "#0066cc"
|
|
navy: "#003366"
|
|
gray: "#666666"
|
|
light-gray: "#f5f5f5"
|
|
primary: blue
|
|
secondary: gray
|
|
success: "#28a745"
|
|
info: blue
|
|
warning: "#ffc107"
|
|
danger: "#dc3545"
|
|
foreground: navy
|
|
background: "#ffffff"
|
|
|
|
typography:
|
|
fonts:
|
|
- family: Inter
|
|
source: google
|
|
weight: [400, 500, 600, 700]
|
|
style: [normal, italic]
|
|
- family: Fira Code
|
|
source: google
|
|
weight: [400, 500]
|
|
base:
|
|
family: Inter
|
|
size: 16px
|
|
line-height: 1.5
|
|
headings:
|
|
family: Inter
|
|
weight: 600
|
|
line-height: 1.2
|
|
monospace:
|
|
family: Fira Code
|
|
size: 14px
|
|
```
|
|
|
|
## Tips
|
|
|
|
- **Use __file__**: Most reliable way to locate `_brand.yml` in app directory
|
|
- **Start simple**: Begin with colors and one font
|
|
- **Test paths**: If brand doesn't apply, try explicit paths
|
|
- **Version control**: Include `_brand.yml` in git repository
|
|
- **Precompile for production**: Use `.to_css()` to avoid runtime Sass compilation
|
|
|
|
```python
|
|
# Development
|
|
theme = ui.Theme.from_brand(__file__)
|
|
|
|
# Production (precompile)
|
|
theme_css = ui.Theme.from_brand(__file__).to_css()
|
|
# Save to static/theme.css, then reference in production
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
**Theme not applying?**
|
|
- Check file is named `_brand.yml` (with underscore)
|
|
- Verify `libsass` is installed: `pip install libsass`
|
|
- Try explicit path: `ui.Theme.from_brand("path/to/_brand.yml")`
|
|
- Check for YAML syntax errors
|
|
|
|
**Colors not matching?**
|
|
- Ensure hex colors have quotes: `"#0066cc"`
|
|
- Verify color names match palette definitions
|
|
- Check semantic colors reference valid palette names
|
|
|
|
**Fonts not loading?**
|
|
- Verify Google Fonts spelling and availability
|
|
- Ensure `source: google` is specified
|
|
- Check font family names match exactly
|
|
- Internet connection required for Google Fonts
|
|
|
|
**Import errors?**
|
|
- Install theme support: `pip install "shiny[theme]"`
|
|
- Or install libsass separately: `pip install libsass`
|
|
|
|
## Performance Considerations
|
|
|
|
For production apps with many instances, precompile the theme:
|
|
|
|
```python
|
|
# build_theme.py
|
|
from shiny import ui
|
|
|
|
theme = ui.Theme.from_brand("_brand.yml")
|
|
css = theme.to_css()
|
|
|
|
with open("static/brand-theme.css", "w") as f:
|
|
f.write(css)
|
|
|
|
# Then in app.py, reference the CSS file directly
|
|
# This avoids runtime Sass compilation overhead
|
|
```
|