Skip to main content

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.

Python 3.10+ License: MIT Version

โœจ 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?

  1. Earlier error detection - Components validated at startup
  2. Better caching - Invalidates cache when files change
  3. Type safety - Pydantic validates all configuration
  4. Immutability - Configuration can't be accidentally modified
  5. 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 templates
  • styles/*.css - CSS stylesheets
  • scripts/*.js - JavaScript files

How it works:

  1. FileWatcher detects file changes (using watchdog or polling)
  2. DevServer invalidates cache for affected components
  3. Streamlit auto-reruns and shows updated component
  4. 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 accepted
  • SERVER_WINS - Server state maintained
  • LATEST_WINS - Most recent update wins
  • MERGE - Intelligent merging of changes
  • CUSTOM - 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 present
  • TYPE - Value type checking (str, int, float, bool, list, dict)
  • PATTERN - Regex pattern matching
  • RANGE - Min/max value validation
  • ENUM - Value must be in allowed list
  • CUSTOM - 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:

  1. Basic Button (examples/basic_button/)

    • Simple component with HTML/CSS/JS
    • Click counting and callbacks
    • Component customization
  2. Tailwind Card (examples/tailwind_card/)

    • External framework integration
    • Product card with pricing
    • Interactive builder
  3. 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.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. 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


Made with โค๏ธ for the Streamlit community

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

streamlit_html_components-0.3.2.tar.gz (64.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

streamlit_html_components-0.3.2-py3-none-any.whl (63.2 kB view details)

Uploaded Python 3

File details

Details for the file streamlit_html_components-0.3.2.tar.gz.

File metadata

File hashes

Hashes for streamlit_html_components-0.3.2.tar.gz
Algorithm Hash digest
SHA256 0cdff277ee3be77ef33dbdfe7a6ef9c0c08e02f82cebaf9a71caf048239edac1
MD5 b8e9bb6cbe945d8f5319723960ff5238
BLAKE2b-256 6e8f7180d4a080b40ce4e01b86185674e34bef146bde62f08bc35a4ab8223078

See more details on using hashes here.

File details

Details for the file streamlit_html_components-0.3.2-py3-none-any.whl.

File metadata

File hashes

Hashes for streamlit_html_components-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 3daed495d2a48ca2cea41a25c38f7b719982cdf537b83b1a5ad492b23f5b440f
MD5 90f93fc49cd66172c6d85a8f9046efe6
BLAKE2b-256 1481cd64ff3a7e9ac0dd6e3fae37e5dca255bd61be515d8f9ae5487dc6819dc6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page