Use traditional HTML/CSS/JS file structure with Streamlit - now with hot reload, state management, and validation
Project description
streamlit-html-components
Use traditional HTML/CSS/JS file structure with Streamlit while keeping the benefits of free Streamlit deployment.
โจ What's New in v0.3.0
- ๐ฅ Hot Reload - Component files auto-reload during development
- ๐ State Management - Real-time state sync with conflict resolution
- ๐ Event Replay - Record and replay component events
- โ Props Validation - JSON Schema validation for component props
- ๐ฆ Component Registry - Early validation at startup
Features
Core Features
- ๐ Organized File Structure - Separate HTML/CSS/JS files (traditional web development workflow)
- ๐ Template Variables - Jinja2-powered props and data binding
- ๐จ Framework Integration - Easy setup with Tailwind, Bootstrap, Bulma, and more
- โก Performance Caching - Multi-level caching for fast rendering
- ๐ Streamlit Cloud Ready - Works with free Streamlit deployment
- ๐ก๏ธ Security Built-in - XSS prevention and input validation
Advanced Features (v0.3.0)
- ๐ฅ Hot Reload - Component files auto-reload during development (no restart needed!)
- ๐ Enhanced Bidirectional Communication - State management, event replay, conflict resolution
- โ Props Validation - JSON Schema or manual validation rules
- ๐ฆ Component Registry - Validate components at startup, not render time
- ๐ Fuzzy Matching - Smart error messages with suggestions
- ๐ State History - Track and rollback state changes
- ๐ฏ Event Recording - Automatic event history with replay capability
Installation
pip install streamlit-html-components
Or install from source:
git clone https://github.com/cjcarito/streamlit-html-components
cd streamlit-html-components
pip install -e .
Quick Start
1. Create Your Component Files
my_app/
โโโ app.py
โโโ components/
โโโ templates/
โ โโโ button.html
โโโ styles/
โ โโโ button.css
โโโ scripts/
โโโ button.js
templates/button.html:
<button class="custom-btn" id="myBtn">
{{ text }}
</button>
styles/button.css:
.custom-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 12px 32px;
border: none;
border-radius: 8px;
cursor: pointer;
}
scripts/button.js:
document.getElementById('myBtn').addEventListener('click', function() {
alert('Button clicked!');
});
2. Use in Your Streamlit App
app.py:
import streamlit as st
from streamlit_html_components import render_component, configure
# Configure component directories
configure(
templates_dir='components/templates',
styles_dir='components/styles',
scripts_dir='components/scripts'
)
st.title("My Custom Button")
# Render your component
render_component(
'button', # Component name (matches template filename)
props={'text': 'Click me!'}, # Variables passed to template
height=100
)
3. Run Your App
streamlit run app.py
That's it! Your custom HTML/CSS/JS component is now running in Streamlit.
Modern v2 API (Recommended)
The v2 API provides enhanced features with better validation, immutable configuration, and improved performance.
Key Benefits
- โ Component Registry - Validate components at startup, not render time
- ๐ Immutable Configuration - Pydantic-based type-safe configuration
- ๐ Better Caching - Cache keys based on actual file content
- ๐ Better Error Messages - Detailed validation errors with suggestions
- ๐ Auto-discovery - Automatically find and register components
Quick Start with v2
import streamlit as st
from streamlit_html_components import configure_v2, render_component_v2
# Configure with auto-discovery
configure_v2(
templates_dir='components/templates',
styles_dir='components/styles',
scripts_dir='components/scripts',
frameworks=['tailwind'],
auto_discover=True # Automatically register all components
)
st.title("My App with v2 API")
# Render component (validated at startup)
render_component_v2('button', props={'text': 'Click me!'})
v2 API Reference
configure_v2()
Modern configuration with Pydantic validation:
from streamlit_html_components import configure_v2
configure_v2(
templates_dir='components/templates',
styles_dir='components/styles',
scripts_dir='components/scripts',
frameworks=['tailwind', 'bootstrap'],
# Cache settings
enable_cache=True,
cache_max_size_mb=100,
cache_ttl_seconds=300,
# Security settings
enable_csp=True,
allowed_origins=['*'],
validate_paths=True,
# Auto-discovery
auto_discover=True # Scan and register all components
)
render_component_v2()
Render components with registry validation:
from streamlit_html_components import render_component_v2
render_component_v2(
component_name='button',
props={'text': 'Click me'},
height=100,
cache=True,
on_event=callback_function
)
register_component()
Manually register components:
from streamlit_html_components import register_component
register_component(
name='custom_button',
template='button.html',
styles=['button.css', 'animations.css'],
scripts=['button.js'],
validate=True # Validate files exist at registration time
)
list_components()
List all registered components:
from streamlit_html_components import list_components
components = list_components()
print(components) # ['button', 'card', 'hero', ...]
get_component_info()
Get component metadata:
from streamlit_html_components import get_component_info
info = get_component_info('button')
print(info.template) # 'button.html'
print(info.styles) # ['button.css']
print(info.scripts) # ['button.js']
Migration from v1 to v2
The v2 API is backward compatible. You can use both APIs in the same app:
from streamlit_html_components import (
configure, render_component, # v1 API
configure_v2, render_component_v2 # v2 API
)
# Use v1 for backward compatibility
configure(templates_dir='old_components/templates')
render_component('legacy_button', props={'text': 'Old API'})
# Use v2 for new components
configure_v2(templates_dir='new_components/templates', auto_discover=True)
render_component_v2('modern_button', props={'text': 'New API'})
Why migrate to v2?
- Earlier error detection - Components validated at startup
- Better caching - Invalidates cache when files change
- Type safety - Pydantic validates all configuration
- Immutability - Configuration can't be accidentally modified
- Better DX - Auto-discovery reduces boilerplate
Core API (v1 - Legacy)
render_component()
The main function to render components:
render_component(
component_name: str, # Name of component (e.g., 'button')
props: Dict[str, Any] = None, # Variables for template
# Directory overrides (optional)
templates_dir: str = None,
styles_dir: str = None,
scripts_dir: str = None,
# Asset control (optional)
styles: List[str] = None, # CSS files to load
scripts: List[str] = None, # JS files to load
frameworks: List[str] = None, # External frameworks
# Display options
height: int = None, # Component height in pixels
width: int = None,
scrolling: bool = False,
key: str = None, # Streamlit component key
# Performance
cache: bool = None, # Enable caching
cache_ttl: int = None, # Cache time-to-live (seconds)
# Interactivity
on_event: Callable = None # Callback for JS events
)
configure()
Set global defaults for all components:
from streamlit_html_components import configure
configure(
templates_dir='components/templates',
styles_dir='components/styles',
scripts_dir='components/scripts',
default_cache=True,
external_frameworks=['tailwind']
)
add_framework()
Add custom CSS/JS frameworks:
from streamlit_html_components import add_framework
add_framework(
'my_framework',
css_urls=['https://cdn.example.com/framework.css'],
js_urls=['https://cdn.example.com/framework.js']
)
Template Features (Jinja2)
Variable Interpolation
<h1>{{ title }}</h1>
<p>{{ description }}</p>
Conditionals
{% if user_logged_in %}
<button>Logout</button>
{% else %}
<button>Login</button>
{% endif %}
Loops
<ul>
{% for item in items %}
<li>{{ item.name }}: {{ item.price | currency }}</li>
{% endfor %}
</ul>
Custom Filters
Built-in filters:
{{ price | currency }}โ "$1,234.56"{{ date | date("%B %d, %Y") }}โ "December 10, 2025"{{ 0.156 | percentage }}โ "15.6%"
Template Inheritance
base.html:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
button.html:
{% extends "base.html" %}
{% block title %}My Button{% endblock %}
{% block content %}
<button>{{ text }}</button>
{% endblock %}
External Framework Integration
Tailwind CSS
configure(external_frameworks=['tailwind'])
render_component('card', props={
'title': 'Product Card',
'price': 99.99
})
card.html:
<div class="max-w-sm rounded-lg shadow-lg bg-white p-6">
<h2 class="text-2xl font-bold text-gray-800">{{ title }}</h2>
<p class="text-3xl text-indigo-600">{{ price | currency }}</p>
</div>
Bootstrap
configure(external_frameworks=['bootstrap'])
Bulma
configure(external_frameworks=['bulma'])
Custom Frameworks
add_framework(
'my_ui_kit',
css_urls=['https://cdn.example.com/ui-kit.css'],
js_urls=['https://cdn.example.com/ui-kit.js']
)
configure(external_frameworks=['my_ui_kit'])
Caching for Performance
Components are cached by default for optimal performance:
# Enable caching (default)
render_component('button', props={'text': 'Click'}, cache=True)
# Set cache expiration (30 seconds)
render_component('button', props={'text': 'Click'}, cache_ttl=30)
# Disable caching for dynamic content
render_component('live_data', props={'data': data}, cache=False)
# Clear cache manually
from streamlit_html_components import invalidate_cache
invalidate_cache('button') # Clear specific component
invalidate_cache() # Clear all cache
Cache Statistics:
from streamlit_html_components import cache_stats
stats = cache_stats()
# {'total_entries': 5, 'total_size_kb': 45.2, ...}
๐ฅ Hot Reload (v0.3.0)
Enable instant component updates during development - no Streamlit restart needed!
from streamlit_html_components import configure_v2, enable_hot_reload
# Configure your components
configure_v2(
templates_dir='components/templates',
styles_dir='components/styles',
scripts_dir='components/scripts'
)
# Enable hot reload with one line!
enable_hot_reload(verbose=True)
# Now edit your component files and see changes instantly! ๐ฅ
What gets watched:
templates/*.html- Component templatesstyles/*.css- CSS stylesheetsscripts/*.js- JavaScript files
How it works:
- FileWatcher detects file changes (using watchdog or polling)
- DevServer invalidates cache for affected components
- Streamlit auto-reruns and shows updated component
- Zero manual intervention required!
Optional dependency:
pip install watchdog # For instant file detection (recommended)
# Without watchdog, falls back to polling mode (still works!)
๐ Enhanced Bidirectional Communication (v0.3.0)
Basic Communication
Send data from JavaScript to Python:
button.js:
document.getElementById('myBtn').addEventListener('click', function() {
// Send event to Python
window.sendToStreamlit('click', {
clicks: clickCount,
timestamp: new Date().toISOString()
});
});
app.py:
def on_button_click(data):
st.write(f"Received: {data}")
st.session_state.clicks = data['clicks']
render_component(
'button',
props={'text': 'Click me'},
on_event=on_button_click # Python callback
)
State Management (v0.3.0)
Real-time state synchronization with conflict resolution:
from streamlit_html_components.bidirectional import StateManager, ConflictResolution
# Create state manager
state_manager = StateManager(
conflict_resolution=ConflictResolution.MERGE,
max_history=100
)
# Set initial state
state_manager.set_state('counter', {'count': 0, 'step': 1})
# Subscribe to state changes
def on_state_change(snapshot):
st.write(f"State updated: {snapshot.state}")
state_manager.subscribe('counter', on_state_change)
# Sync from client (JavaScript)
success, conflicts = state_manager.sync_from_client(
'counter',
client_state={'count': 5},
client_version=1
)
Conflict Resolution Strategies:
CLIENT_WINS- Client updates always acceptedSERVER_WINS- Server state maintainedLATEST_WINS- Most recent update winsMERGE- Intelligent merging of changesCUSTOM- User-defined resolution function
State Features:
- Version tracking for every state change
- Complete state history with rollback
- State diffing (added/modified/removed)
- Export/import as JSON
- Real-time change notifications
Event Replay (v0.3.0)
Record and replay component events for debugging and testing:
from streamlit_html_components.bidirectional import get_bridge
bridge = get_bridge()
# Events are automatically recorded
bridge.handle_event('my_component', {
'event': 'click',
'data': {'button_id': 'submit'}
})
# Get event history
events = bridge.get_event_history('my_component')
# Replay all events
bridge.replay_events('my_component')
# Export events as JSON
json_data = bridge.export_events('my_component')
โ Props Validation (v0.3.0)
Validate component props with JSON Schema or manual rules:
JSON Schema Validation
from streamlit_html_components import register_component, PropsSchema
# Define schema
schema = PropsSchema({
"type": "object",
"properties": {
"name": {"type": "string", "minLength": 1},
"age": {"type": "integer", "minimum": 0},
"email": {"type": "string", "format": "email"}
},
"required": ["name", "email"]
})
# Register component with validation
register_component(
name='user_form',
template='user_form.html',
props_schema=schema
)
# Invalid props will raise InvalidPropsError
render_component_v2('user_form', props={
'name': '', # โ Fails: minLength validation
'age': -5, # โ Fails: minimum validation
})
Manual Validation Rules
from streamlit_html_components import PropsSchema, ValidationRule, ValidationType
schema = PropsSchema()
# Add validation rules
schema.add_rule(ValidationRule(
prop_name='email',
validation_type=ValidationType.PATTERN,
rule=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
error_message='Must be a valid email'
))
schema.add_rule(ValidationRule(
prop_name='age',
validation_type=ValidationType.RANGE,
rule={'min': 0, 'max': 120},
error_message='Age must be between 0 and 120'
))
# Validate props
is_valid, errors = schema.validate({'email': 'test@example.com', 'age': 25})
Validation Types:
REQUIRED- Field must be presentTYPE- Value type checking (str, int, float, bool, list, dict)PATTERN- Regex pattern matchingRANGE- Min/max value validationENUM- Value must be in allowed listCUSTOM- Custom validation function
Advanced Usage
Multiple CSS/JS Files
render_component(
'complex_component',
styles=['common', 'component', 'theme'],
scripts=['utils', 'component', 'init']
)
Conditional Asset Loading
# Skip CSS loading
render_component('text_only', styles=[])
# Skip JS loading
render_component('static_card', scripts=[])
Dynamic Props
import streamlit as st
user_name = st.text_input("Your name")
user_email = st.text_input("Your email")
render_component('profile_card', props={
'name': user_name,
'email': user_email,
'joined_date': datetime.now()
})
Examples
The package includes complete working examples:
-
Basic Button (
examples/basic_button/)- Simple component with HTML/CSS/JS
- Click counting and callbacks
- Component customization
-
Tailwind Card (
examples/tailwind_card/)- External framework integration
- Product card with pricing
- Interactive builder
-
Payslip Integration (
examples/payslip_integration/)- Real-world use case
- Integration with existing Streamlit app
To run examples:
cd examples/basic_button
streamlit run app.py
File Organization Conventions
Auto-discovery
By default, render_component('button') automatically loads:
templates/button.html(required)styles/button.css(optional)scripts/button.js(optional)
Explicit Loading
render_component(
'button',
styles=['button', 'animations'], # Load multiple CSS files
scripts=['button', 'utils'] # Load multiple JS files
)
Custom Directories
render_component(
'button',
templates_dir='custom/templates',
styles_dir='custom/styles',
scripts_dir='custom/scripts'
)
Security
XSS Prevention
- Automatic HTML escaping via Jinja2 auto-escaping
- Props validation and sanitization
- Path traversal protection
Safe Template Variables
<!-- Automatically escaped -->
<div>{{ user_input }}</div>
<!-- Explicit unsafe (use carefully) -->
<div>{{ trusted_html | safe }}</div>
API Reference
Core Functions (v2 - Recommended)
| Function | Description |
|---|---|
configure_v2() |
Configure with Pydantic validation and auto-discovery |
render_component_v2() |
Render component with registry validation |
register_component() |
Manually register a component |
list_components() |
List all registered components |
get_component_info() |
Get component metadata |
get_config_v2() |
Get current v2 configuration |
get_registry() |
Get component registry instance |
Core Functions (v1 - Legacy)
| Function | Description |
|---|---|
render_component() |
Render an HTML component |
configure() |
Set global configuration |
add_framework() |
Add custom framework |
Cache Management
| Function | Description |
|---|---|
invalidate_cache() |
Clear component cache |
cache_stats() |
Get cache statistics |
Configuration
| Function | Description |
|---|---|
get_config() |
Get current configuration |
reset_config() |
Reset to defaults |
Exceptions
| Exception | Raised When |
|---|---|
ComponentNotFoundError |
Template file not found |
AssetNotFoundError |
CSS/JS file not found |
TemplateSyntaxError |
Template syntax error |
InvalidPropsError |
Props validation fails |
ConfigurationError |
Invalid configuration |
Troubleshooting
Component not rendering
# Check configuration
from streamlit_html_components import get_config
config = get_config()
print(config)
# Verify file paths
import os
print(os.listdir('templates'))
CSS/JS not loading
# Explicit file specification
render_component(
'button',
styles=['button'], # Explicitly specify CSS file
scripts=['button'] # Explicitly specify JS file
)
Cache issues
# Clear cache
from streamlit_html_components import invalidate_cache
invalidate_cache()
# Disable caching for debugging
render_component('button', cache=False)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Built with Streamlit
- Template engine powered by Jinja2
- Inspired by the need for traditional web development workflows in Streamlit
Support
- Documentation: GitHub Repository
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with โค๏ธ for the Streamlit community
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file streamlit_html_components-0.3.1.tar.gz.
File metadata
- Download URL: streamlit_html_components-0.3.1.tar.gz
- Upload date:
- Size: 64.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf3a3611e96598580515e844b0f0ea45685a79296a94b4254ba8164871b873ff
|
|
| MD5 |
c60339a1e6e3505f054d554cd7022402
|
|
| BLAKE2b-256 |
1b2b80d8864eafe400a37f974c004fab2bf704b20c9d90c25bfd5de739a68b7e
|
File details
Details for the file streamlit_html_components-0.3.1-py3-none-any.whl.
File metadata
- Download URL: streamlit_html_components-0.3.1-py3-none-any.whl
- Upload date:
- Size: 63.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7718abe539dc1ef4897b9ff1ee8197bc84437e78a9ba0860521c67894b839112
|
|
| MD5 |
448daa54d01539c39bcfd6805f485850
|
|
| BLAKE2b-256 |
4d7afcc3a7e5219930ea76b1c4b9572e1db974da063e39d8675c8942f6a96c5a
|