# Animaid
A GUI toolkit for animating Python data structures.
## Overview
Animaid provides tools to visualize and render Python data structures as styled HTML for educational and presentation purposes. It includes classes that subclass Python's built-in types:
- **HTMLString** - A `str` subclass for rendering styled text
- **HTMLInt** - An `int` subclass for formatted numbers (currency, percentages, ordinals)
- **HTMLFloat** - A `float` subclass for decimal formatting and units
- **HTMLList** - A `list` subclass for rendering lists with layout options
- **HTMLDict** - A `dict` subclass for rendering key-value pairs
- **HTMLTuple** - A `tuple` subclass with support for named tuples
- **HTMLSet** - A `set` subclass for rendering unique items as tags or pills
- **App** - A Tkinter-like interactive GUI environment using HTML
- **Window** - Runtime window control for theming, title, and size
- **WindowConfig** - Configuration for window initialization
All classes support a fluent API for chaining style methods and are compatible with [Jinja2](https://jinja.palletsprojects.com/) templates via the `__html__()` protocol.
The `App` class provides a real-time browser-based display where you can add, update, and remove AnimAID objects programmatically with live updates via WebSocket.
## Installation
```bash
pip install animaid
```
Or for development:
```bash
git clone https://github.com/jdrumgoole/animaid.git
cd animaid
uv pip install -e ".[dev,docs]"
```
## Quick Start
### HTMLString
Style text with CSS properties:
```python
from animaid import HTMLString, Color, Size
# Basic styling with strings
s = HTMLString("Hello, World!").bold().color("blue")
print(s.render())
# Hello, World!
# Using CSS types for type-safe styling
styled = (
HTMLString("Important!")
.bold()
.italic()
.color(Color.red)
.background(Color.hex("#ffff00"))
.padding(Size.px(10))
)
```
### HTMLList
Render lists with flexible layouts:
```python
from animaid import HTMLList, Size, Color, Border
# Horizontal list with styled items
items = HTMLList(["Apple", "Banana", "Cherry"])
items = items.horizontal().gap(Size.px(10)).item_padding(Size.px(8))
print(items.render())
# Grid layout with CSS types
grid = (
HTMLList(["A", "B", "C", "D", "E", "F"])
.grid(3)
.gap(Size.px(5))
.item_border(Border().solid().color(Color.gray))
)
```
### HTMLDict
Display key-value pairs in multiple formats:
```python
from animaid import HTMLDict, Color, Size, Border
# Definition list format (default)
d = HTMLDict({"name": "Alice", "role": "Developer"})
d = d.key_bold().key_color(Color.hex("#333")).value_color(Color.hex("#666"))
print(d.render())
# Table format
table = HTMLDict({"x": 1, "y": 2, "z": 3}).as_table()
# Card layout with CSS types
card = (
HTMLDict({"Name": "Alice", "Email": "alice@example.com"})
.key_bold()
.padding(Size.px(12))
.border(Border().solid().color(Color.hex("#ccc")))
.border_radius(Size.px(8))
)
```
### HTMLInt
Format integers with various display options:
```python
from animaid import HTMLInt
# Basic integer with styling
n = HTMLInt(42).bold().blue()
print(n.render())
# 42
# Thousand separators
HTMLInt(1234567).comma().render()
# 1,234,567
# Currency formatting
HTMLInt(1999).currency("$").bold().green()
# $1,999
# Percentage
HTMLInt(85).percent().render()
# 85%
# Ordinal numbers
HTMLInt(1).ordinal().render() # "1st"
HTMLInt(2).ordinal().render() # "2nd"
HTMLInt(3).ordinal().render() # "3rd"
# Badge preset (circular number badge)
HTMLInt(42).badge()
```
### HTMLFloat
Format floating-point numbers with precision control:
```python
from animaid import HTMLFloat
# Basic float with styling
f = HTMLFloat(3.14159).bold().render()
# 3.14159
# Control decimal places
HTMLFloat(3.14159).decimal(2).render()
# 3.14
# Thousand separators
HTMLFloat(1234567.89).comma().render()
# 1,234,567.89
# Percentage (multiplies by 100)
HTMLFloat(0.856).percent().render()
# 85.60%
# Currency
HTMLFloat(19.99).currency("$").render()
# $19.99
# Scientific notation
HTMLFloat(0.000123).scientific().render()
# 1.23e-04
# Add units
HTMLFloat(72.5).unit("kg").render()
# 72.5 kg
```
### HTMLTuple
Render tuples with flexible layouts and named tuple support:
```python
from animaid import HTMLTuple
from collections import namedtuple
# Basic tuple
t = HTMLTuple((1, 2, 3))
t.render()
#
(1, 2, 3)
# Horizontal layout with pills
HTMLTuple(("Red", "Green", "Blue")).horizontal().pills()
# Vertical layout
HTMLTuple(("First", "Second", "Third")).vertical().gap("8px")
# Named tuple support with labels
Point = namedtuple('Point', ['x', 'y'])
HTMLTuple(Point(10, 20)).labeled()
# Renders as: x: 10, y: 20
# Custom separator
HTMLTuple((1, 2, 3)).separator(" | ")
# 1 | 2 | 3
# Without parentheses
HTMLTuple((1, 2, 3)).plain()
# 1, 2, 3
```
### HTMLSet
Render unique items as tags or pills:
```python
from animaid import HTMLSet
# Basic set (duplicates automatically removed)
tags = HTMLSet(["Python", "Web", "Python", "HTML"])
# Only contains: Python, Web, HTML
# Pills preset (rounded pill-style items)
HTMLSet({"python", "javascript", "rust"}).pills()
# Tags preset
HTMLSet({"urgent", "bug", "help-wanted"}).tags()
# Horizontal layout with gap
HTMLSet({"A", "B", "C"}).horizontal().gap("10px")
# Custom item styling
HTMLSet({"one", "two", "three"}).item_background("#e3f2fd").item_padding("8px 12px")
```
HTMLSet is reactive - when used with App, adding or removing items automatically updates the browser:
```python
from animaid import App, HTMLSet
with App() as app:
tags = HTMLSet({"python"}).pills()
app.add(tags)
tags.add("javascript") # Browser updates automatically
tags.add("rust") # Browser updates automatically
tags.discard("python") # Browser updates automatically
```
### Nested Structures
Combine types for complex visualizations:
```python
from animaid import HTMLDict, HTMLList, Size, Border, Color
# Dict of Lists
categories = HTMLDict({
"Fruits": HTMLList(["Apple", "Banana"]).horizontal().gap(Size.px(8)),
"Vegetables": HTMLList(["Carrot", "Broccoli"]).horizontal().gap(Size.px(8)),
}).key_bold()
# List of Dicts (cards)
card_border = Border().solid().color(Color.hex("#ddd"))
cards = HTMLList([
HTMLDict({"Name": "Alice", "Role": "Dev"}).key_bold().padding(Size.px(10)).border(card_border),
HTMLDict({"Name": "Bob", "Role": "Design"}).key_bold().padding(Size.px(10)).border(card_border),
]).horizontal().gap(Size.px(16))
```
## CSS Types
Animaid provides type-safe CSS value classes that can be used instead of raw strings. These provide better IDE autocomplete, validation, and documentation.
### Size
Create CSS size values with explicit units:
```python
from animaid import Size
Size.px(16) # "16px"
Size.em(1.5) # "1.5em"
Size.rem(2) # "2rem"
Size.percent(50) # "50%"
Size.vh(100) # "100vh"
Size.vw(80) # "80vw"
Size.auto() # "auto"
```
### Color
Create CSS color values with validation:
```python
from animaid import Color
# Named colors
Color.red # "red"
Color.blue # "blue"
# Hex colors
Color.hex("#ff0000") # "#ff0000"
Color.hex("00f") # "#00f" (auto-adds #)
# RGB/RGBA
Color.rgb(255, 0, 0) # "rgb(255, 0, 0)"
Color.rgba(255, 0, 0, 0.5) # "rgba(255, 0, 0, 0.5)"
# HSL/HSLA
Color.hsl(120, 100, 50) # "hsl(120, 100%, 50%)"
Color.hsla(120, 100, 50, 0.5) # "hsla(120, 100%, 50%, 0.5)"
```
### Border
Create CSS border values with a fluent API:
```python
from animaid import Border, BorderStyle, Color, Size
# Using constructor
Border(Size.px(1), BorderStyle.SOLID, Color.black) # "1px solid black"
# Using fluent API
Border().width(2).solid().color("red") # "2px solid red"
Border().dashed().color(Color.blue) # "1px dashed blue"
# Factory methods
Border.solid(2, "black") # "2px solid black"
Border.dashed(1, "gray") # "1px dashed gray"
```
### Spacing
Create CSS spacing values (padding, margin) for 1-4 edges:
```python
from animaid import Spacing, Size
# Single value (all edges)
Spacing.all(10) # "10px"
# Two values (vertical, horizontal)
Spacing.symmetric(10, 20) # "10px 20px"
# Four values (top, right, bottom, left)
Spacing.edges(10, 20, 10, 20) # "10px 20px 10px 20px"
# Using Size values
Spacing.symmetric(Size.rem(1), Size.rem(2)) # "1rem 2rem"
```
### Enums
CSS enum values for constrained properties:
```python
from animaid import (
Display, FlexDirection, AlignItems, JustifyContent,
FontWeight, FontStyle, TextAlign, TextDecoration
)
# Display
Display.FLEX, Display.GRID, Display.BLOCK, Display.INLINE
# Flexbox
FlexDirection.ROW, FlexDirection.COLUMN
AlignItems.CENTER, AlignItems.STRETCH
JustifyContent.SPACE_BETWEEN, JustifyContent.CENTER
# Typography
FontWeight.BOLD, FontWeight.NORMAL
FontStyle.ITALIC, FontStyle.NORMAL
TextAlign.CENTER, TextAlign.LEFT
TextDecoration.UNDERLINE, TextDecoration.LINE_THROUGH
```
### Backward Compatibility
All methods accept both CSS types and raw strings:
```python
from animaid import HTMLString, Color, Size
# Both work identically:
HTMLString("Hello").color("red") # Using string
HTMLString("Hello").color(Color.red) # Using CSS type
HTMLString("Hello").font_size("16px") # Using string
HTMLString("Hello").font_size(Size.px(16)) # Using CSS type
```
## App - Interactive Display
The `App` class provides a Tkinter-like interactive GUI environment using HTML. The browser becomes the display surface, and AnimAID objects become widgets that can be added, updated, and removed programmatically with real-time visual feedback.
### Basic Usage
```python
from animaid import App, HTMLString, HTMLList
# Create and start the server (opens browser automatically)
app = App()
app.run()
# Add items - browser updates in real-time
app.add(HTMLString("Hello World!").bold().xl())
app.add(HTMLString("This updates live").italic().blue())
app.add(HTMLList(["Apple", "Banana", "Cherry"]).pills())
# Update an existing item
item_id = app.add(HTMLString("Loading...").muted())
app.update(item_id, HTMLString("Done!").success())
# Remove items - by ID or by object
app.remove(item_id) # By ID
app.remove(my_item) # By object reference
app.clear(my_item) # Alias for remove
app.clear_all() # Clear all items
# Stop the server when done
app.stop()
```
### Context Manager
Use the context manager for automatic cleanup:
```python
from animaid import App, HTMLString
with App() as app:
app.add(HTMLString("Temporary display").bold())
app.add(HTMLString("Server stops when context exits").muted())
input("Press Enter to exit...")
# Server stops automatically when context exits
```
### Configuration Options
```python
# Custom port and title
app = App(port=8300, title="My App")
# With theme
app = App(theme="dark")
# With window size
app = App(width=1280, height=720)
# Using WindowConfig for presets
from animaid import WindowConfig
app = App(window=WindowConfig.compact(title="Tool"))
# Disable auto-opening browser
app = App(auto_open=False)
app.run()
print(f"Open browser at: {app.url}")
```
### Window Controls
Access the `window` property to control the browser window at runtime:
```python
from animaid import App, HTMLString
with App() as app:
# Switch to dark theme
app.window.dark()
# Update the title dynamically
for i in range(100):
app.window.set_title(f"Processing... {i}%")
app.window.set_title("Complete!")
# Resize the window
app.window.resize(1280, 720)
# Switch themes
app.window.set_theme("light") # or "dark" or "auto"
# Set background color
app.window.set_background("#1a1a2e")
# Request fullscreen (browser may require user interaction)
app.window.fullscreen()
```
#### Window Methods
| Method | Description |
|--------|-------------|
| `set_title(title)` | Update the browser tab title |
| `set_theme(theme)` | Set theme ('light', 'dark', 'auto') |
| `dark()` | Switch to dark theme |
| `light()` | Switch to light theme |
| `resize(width, height)` | Resize the window |
| `set_background(color)` | Set the page background color |
| `set_favicon(url)` | Set the page favicon |
| `fullscreen()` | Request fullscreen mode |
| `on_resize(callback)` | Register resize callback |
| `on_close(callback)` | Register close callback |
#### WindowConfig Presets
```python
from animaid import WindowConfig
# Factory presets
config = WindowConfig.compact(title="Tool") # 600x400
config = WindowConfig.standard(title="App") # 1024x768
config = WindowConfig.wide(title="Dashboard") # 1280x720
config = WindowConfig.dark(title="Dark Mode") # Dark theme
```
#### Theme Toggle Example
```python
from animaid import App, HTMLButton
with App() as app:
toggle = HTMLButton("Toggle Theme", variant="primary")
app.add(toggle)
dark = False
while True:
event = app.wait_for_event(timeout=0.5)
if event and event.event_type == "click":
dark = not dark
if dark:
app.window.dark()
else:
app.window.light()
```
### API Methods
| Method | Description |
|--------|-------------|
| `run()` | Start server and open browser |
| `stop()` | Stop the server |
| `add(item, id=None)` | Add item, returns ID |
| `update(id, item)` | Update item by ID |
| `remove(item_or_id)` | Remove item by ID or object |
| `clear(item_or_id)` | Remove item by ID or object (alias for remove) |
| `clear_all()` | Remove all items |
| `get(id)` | Get item by ID |
| `items()` | Get all (id, item) pairs |
| `refresh(id)` | Re-render and broadcast a single item |
| `refresh_all()` | Re-render and broadcast all items |
### Properties
| Property | Description |
|----------|-------------|
| `url` | Server URL (e.g., `http://127.0.0.1:8200`) |
| `is_running` | Whether server is running |
| `title` | Display title |
| `port` | Server port |
### Installation
The `App` class requires the tutorial dependencies:
```bash
pip install animaid[tutorial]
# or with uv
uv pip install animaid[tutorial]
```
### Reactive Updates
Mutable HTML objects (`HTMLList`, `HTMLDict`, `HTMLSet`) automatically notify the App display when their contents change. This means you can mutate these objects directly and the browser will update in real-time without calling `update()`.
```python
from animaid import App, HTMLList, HTMLDict, HTMLSet
app = App()
app.run()
# Add a mutable list
scores = HTMLList([10, 20, 30]).pills()
app.add(scores)
# Mutate the list - browser updates automatically!
scores.append(40) # Browser shows [10, 20, 30, 40]
scores[0] = 100 # Browser shows [100, 20, 30, 40]
scores.pop() # Browser shows [100, 20, 30]
# Same for dicts
data = HTMLDict({"score": 0, "level": 1}).card()
app.add(data)
data["score"] = 500 # Browser updates automatically
# And sets
tags = HTMLSet({"python", "html"}).pills()
app.add(tags)
tags.add("css") # Browser updates automatically
```
**Styling updates work on all types:** All HTML types (`HTMLString`, `HTMLInt`, `HTMLFloat`, `HTMLTuple`, `HTMLList`, `HTMLDict`, `HTMLSet`) automatically notify App when their styles change:
```python
from animaid import App, HTMLString, HTMLInt
app = App()
app.run()
# Style changes trigger automatic updates
message = HTMLString("Hello")
app.add(message)
message.bold() # Browser updates automatically
message.red() # Browser updates automatically
number = HTMLInt(42)
app.add(number)
number.badge() # Browser updates automatically
```
**Immutable types need update() for data changes:** Since `HTMLString`, `HTMLInt`, `HTMLFloat`, and `HTMLTuple` inherit from Python's immutable types, you cannot change their underlying data. To display different content, use the `update()` method:
```python
from animaid import App, HTMLString
app = App()
app.run()
message = HTMLString("Loading...").muted()
item_id = app.add(message)
# Must use update() to change the content (not just style)
app.update(item_id, HTMLString("Complete!").success())
```
**Manual refresh:** If you modify an object through a method that bypasses the notification system, use `refresh()` or `refresh_all()`:
```python
# Force re-render of a specific item
app.refresh(item_id)
# Force re-render of all items
app.refresh_all()
```
## Demo Programs
AnimAID includes several demo programs that showcase its interactive capabilities. Run them to see real-time updates in action!
### Running Demos
List all available demos:
```bash
animaid-demo --list
```
Run a specific demo:
```bash
animaid-demo countdown_timer
```
Or run directly with Python:
```bash
python demos/countdown_timer.py
```
### Available Demos
| Demo | Description | Source |
|------|-------------|--------|
| `countdown_timer` | Real-time countdown with color transitions (green → yellow → red) | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/countdown_timer.py) |
| `live_list` | Reactive shopping cart showing `.append()` and `.pop()` | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/live_list.py) |
| `score_tracker` | Game score tracking with automatic dict updates | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/score_tracker.py) |
| `sorting_visualizer` | Bubble sort algorithm with step-by-step visualization | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/sorting_visualizer.py) |
| `dashboard` | Multi-type dashboard with HTMLString, HTMLDict, HTMLList, HTMLSet | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/dashboard.py) |
| `typewriter` | Typewriter effect with progressive styling | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/typewriter.py) |
| `todo_app` | Interactive todo list with CRUD operations | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/todo_app.py) |
| `data_pipeline` | ETL pipeline progress tracking | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/data_pipeline.py) |
### Input Widget Demos
| Demo | Description | Source |
|------|-------------|--------|
| `input_button` | Button styles, sizes, and click event handling | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/input_button.py) |
| `input_text` | Text input with live typing feedback and validation | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/input_text.py) |
| `input_checkbox` | Checkbox toggles and multi-checkbox patterns | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/input_checkbox.py) |
| `input_select` | Select dropdowns with dynamic updates | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/input_select.py) |
| `input_slider` | RGB color mixer with sliders | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/input_slider.py) |
| `input_greeter` | Interactive greeter with text input and button | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/input_greeter.py) |
| `input_counter` | Counter with increment/decrement buttons | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/input_counter.py) |
| `input_form` | Registration form with multiple input types | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/input_form.py) |
### Container Demos
| Demo | Description | Source |
|------|-------------|--------|
| `container_layout` | HTMLRow and HTMLColumn container layouts | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/container_layout.py) |
| `container_card` | HTMLCard with shadows, borders, and presets | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/container_card.py) |
| `container_divider` | HTMLDivider for visual content separation | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/container_divider.py) |
| `container_spacer` | HTMLSpacer for fixed and flexible spacing | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/container_spacer.py) |
| `container_row_column` | Flexbox layouts with alignment options | [View Code](https://github.com/jdrumgoole/animaid/blob/main/demos/container_row_column.py) |
Each demo opens a browser window and shows real-time updates as Python code executes.
### Demo Gallery
**Countdown Timer** - Color transitions from green to yellow to red ([source](https://github.com/jdrumgoole/animaid/blob/main/demos/countdown_timer.py))

**Sorting Visualizer** - Bubble sort with step-by-step animation ([source](https://github.com/jdrumgoole/animaid/blob/main/demos/sorting_visualizer.py))

**Dashboard** - Multiple HTML types updating together ([source](https://github.com/jdrumgoole/animaid/blob/main/demos/dashboard.py))

**Todo App** - Interactive task management ([source](https://github.com/jdrumgoole/animaid/blob/main/demos/todo_app.py))

### Input Widget Gallery
**Button Showcase** - Button styles, sizes, and click events ([source](https://github.com/jdrumgoole/animaid/blob/main/demos/input_button.py))

**Text Input** - Live character counting and validation ([source](https://github.com/jdrumgoole/animaid/blob/main/demos/input_text.py))

**Checkbox Showcase** - Toggle handling and preferences panel ([source](https://github.com/jdrumgoole/animaid/blob/main/demos/input_checkbox.py))

**Select Dropdown** - Dynamic selection with live updates ([source](https://github.com/jdrumgoole/animaid/blob/main/demos/input_select.py))

**RGB Color Mixer** - Sliders for real-time color mixing ([source](https://github.com/jdrumgoole/animaid/blob/main/demos/input_slider.py))

**Interactive Counter** - Increment/decrement with buttons ([source](https://github.com/jdrumgoole/animaid/blob/main/demos/input_counter.py))

## Interactive Tutorial
AnimAID includes a comprehensive web-based tutorial that lets you explore all features interactively.
### Starting the Tutorial
```bash
# Install with tutorial dependencies
pip install animaid[tutorial]
# Start the tutorial (opens browser automatically)
animaid-tutorial
# Or specify a custom port
animaid-tutorial --port 8300
```
### Tutorial Features
The tutorial provides five main sections:
**Python Objects Tab**
- Unified interface for all HTML types (HTMLString, HTMLList, HTMLDict, HTMLInt, HTMLFloat, HTMLTuple, HTMLSet)
- Dropdown selector to switch between object types
- Type-specific controls and presets
- Live preview, Python code, and HTML output

**Input Widgets Tab**
- Interactive input widgets: Button, TextInput, Checkbox, Slider, Select
- Widget-specific controls and presets
- Live demonstration of event handling
- Generated Python code for each widget

**Containers Tab**
- Layout containers: HTMLRow, HTMLColumn, HTMLCard, HTMLDivider, HTMLSpacer
- Container-specific controls and presets
- Live preview of flexbox layouts
- Generated Python code for each container

**Dict of Lists Tab**
- Visualize nested dictionaries containing lists
- Configure list styling (horizontal, vertical, pills, cards)
- See how nested structures render
**List of Dicts Tab**
- Visualize lists of dictionaries as cards
- Configure card styling and layout
- Perfect for displaying collections of records
### What You Can Do
- **Experiment**: Adjust any property and see instant results
- **Copy Code**: Generated Python code is ready to use in your projects
- **Learn**: See how each style method affects the HTML output
- **Explore Presets**: Quick buttons for common styling patterns
## Contents
```{toctree}
:maxdepth: 2
:caption: Contents
tutorial
demos
input-widgets
containers
api
```
## Indices and tables
- {ref}`genindex`
- {ref}`modindex`
- {ref}`search`