Skip to main content

CS UI

A Python UI framework built on Flet, providing a rich set of pre-styled, ready-to-use components with enhanced defaults and a clean package structure.

Features

  • Batteries included — from ui import * re-exports everything from Flet plus all CS UI components
  • Design tokens + Material 3 theming — no raw color / radius / font-size literals inside components. A single brand seed (color_scheme_seed) derives the whole M3 color scheme, so light/dark and brand-color switching are framework-level operations, not per-widget recoloring
  • Inheritance-based — static components subclass Flet native controls (e.g., Button(ft.Button), Text(ft.Text))
  • Smart defaults — components come with sensible styling defaults (colors, sizes, border-radius, etc.) for rapid prototyping
  • Categorized modules — components organized by function: chart, display, feedback, input, layout, navigation
  • Declarative routing — root-view routing via ft.Router(routes, manage_views=False) + page.render
  • Working page overlays — under the root-view path, page.overlay / page.show_dialog actually render, so the MultiSelect dropdown, the DateInput calendar, and the DateTimeInput calendar-plus-time-wheel panel can truly float above the content (a collapsed field is all the layout height the widget takes; opening it never pushes the content below)
  • Two component paradigms — static components subclass native Flet controls; interactive ones are @ft.component functions whose state is declared with hooks (use_state / use_memo / use_effect)
  • Charts — Bar, Line, Area, and Scatter chart wrappers via flet-charts, with both numeric and categorical x-axes
  • Desktop-grade timeline — Timeline supports three alignments (rail on the left / rail on the right / alternating around a centered rail); the timestamp can sit in its own column on the opposite side (the default, VS Code style), above the title, inline, or be hidden. Dots come with semantic colors, three variants (filled / outlined / plain) and three sizes; done turns into a check mark automatically and pending appends an "in progress" hollow ring. Rows are clickable end to end — hover highlight and click ripple are painted by Material's ink, so nothing is re-rendered from Python and long lists stay smooth

Requirements

  • Python >= 3.12
  • flet[all] ~= 1.0.5
  • flet-code-editor
  • flet-charts
  • flet-video / flet-audio / flet-webview

Installation

uv pip install cs-ui

Or install from source in editable mode:

git clone https://github.com/icstos/cs-ui.git
cd cs-ui
pip install -e .

Quick Start

import flet as ft
from ui import (
    Button,
    Card,
    Checkbox,
    Column,
    Container,
    Divider,
    Input,
    Router,
    Route,
    Row,
    Text,
)


@ft.component
def HomePage() -> ft.Control:
    name = ft.use_ref(lambda: Input(label="Name", value="Shawn", width=260)).current
    agree = ft.use_ref(lambda: Checkbox(label="I have read the terms")).current

    # Pages return plain controls and draw their own top bar — see the notes below
    return Container(
        expand=True,
        content=Column(
            spacing=0,
            controls=[
                Container(
                    height=56,
                    bgcolor=ft.Colors.WHITE,
                    padding=ft.Padding.symmetric(horizontal=14),
                    content=Row(
                        vertical_alignment=ft.CrossAxisAlignment.CENTER,
                        controls=[Text("CS UI Demo", size=17, weight=ft.FontWeight.W_600)],
                    ),
                ),
                Card(
                    elevation=4,
                    content=Container(
                        padding=24,
                        border_radius=16,
                        content=Column(
                            controls=[
                                Text("CS UI declarative demo", size=24, weight=ft.FontWeight.BOLD),
                                Text("Built on flet 1.0.0.", size=14, color="#6b7280"),
                                Divider(),
                                name.ui(),
                                Row(
                                    spacing=20,
                                    controls=[agree.ui(), ft.Switch(label="A switch")],
                                ),
                                Button("Click me", on_click=lambda _: print("clicked!")),
                            ],
                            spacing=16,
                        ),
                    ),
                ),
            ],
        ),
    )


@ft.component
def App() -> ft.Control:
    return Router([Route(index=True, component=HomePage)], manage_views=False)


def main(page: ft.Page) -> None:
    page.title = "CS UI Demo"
    page.render(App)


if __name__ == "__main__":
    ft.run(main)

Render path: use page.render + Router(manage_views=False), not page.render_views.

Two pitfalls we hit for real:

  1. page.render_views (the view stack behind Router(manage_views=True)) covers the entire page overlay layer — anything appended to page.overlay renders zero pixels, and page.show_dialog is just as dead. It does not raise: len(page.overlay) is correct and callbacks still fire. Floating MultiSelect panels, toasts and dialogs all silently break.
  2. ft.View cannot be used as an ordinary control. Under the root-view path, putting a View into the widget tree makes Flutter throw Bad state: No element on repeat and the whole page turns into a grey box. Likewise ft.AppBar is an AdaptiveControl: it only fits View.appbar and cannot go into Column.controls — draw the top bar with Container + Row instead.

Migration notes: ft.app(main) → ft.run(main); page.add(...) → page.render(Component); page.go(route) → page.navigate(route); hand-maintained page.views → ft.Router(routes, manage_views=False). The bundled ui.Button takes its text as content (not label).

Components come in two flavors: stateless controls (Button / Table / Rating …) subclass Flet controls and are usable immediately; stateful components (Input / Checkbox / Switch / SelectBox / MultiSelect …) are @ft.observable data objects that must be constructed and then rendered via .ui() — putting the object itself in the widget tree yields a blank or grey box.

If the host app has to keep the view stack (manage_views=True), MultiSelect automatically degrades to in-flow expansion (the panel takes layout height and pushes content down); pass float_panel=False to opt out of the overlay explicitly.

Package Structure

src/ui/
├── __init__.py              # re-exports all of flet + every CS UI component (617 names)
├── app.py                   # legacy-style App entry point
├── ft_init.py               # Flet initialization
├── theme.py                 # theme / palette
├── cli.py                   # CLI entry point
├── chart/                   # Charts (via flet-charts)
│   ├── _data.py             #   shared data parsing + palette (numeric / categorical axes)
│   ├── bar_chart.py         #   BarChart
│   ├── line_chart.py        #   LineChart
│   ├── rea_chart.py         #   AreaChart
│   └── scatter_chart.py     #   ScatterChart
├── components/              # Reusable generic controls
│   └── icon_button.py       #   IconButton
├── core/                    # Core utilities
│   ├── config.py            #   configuration
│   ├── constants.py         #   StyleType / FeedbackStyle / ButtonShape …
│   ├── float_layer.py       #   page overlay: overlay_usable / use_float_layer
│   ├── language.py          #   i18n
│   ├── logger.py            #   logging
│   ├── snackbar.py          #   content guard for SnackBar-based components
│   ├── styles.py            #   shared style helpers
│   ├── theme.py             #   ThemeProvider / ThemeSwitcher / use_theme
│   └── tokens.py            #   design tokens: SPACE / RADIUS / FONT_SIZE / semantic colors
├── data/                    # Static assets (fonts, images)
├── display/                 # Display components
│   ├── image.py             #   Image
│   ├── image_gridview.py    #   ImageGridView
│   ├── list_tile.py         #   ListTile
│   ├── log_container.py     #   LogContainer
│   ├── text.py              #   Text / Header_1..5 / Quote / Link / Code / Markdown / Json
│   └── media/               #   Media
│       ├── audio.py         #     Audio / AudioPlayer
│       ├── pdf.py           #     Pdf
│       └── video.py         #     Video
├── feedback/                # Feedback & overlays
│   ├── alert_dialog.py      #   AlertDialog
│   ├── loading.py           #   Loading
│   ├── message.py           #   Message
│   ├── progress_bar.py      #   ProgressBar
│   └── toast.py             #   Toast / toast_success / toast_error …
├── input/                   # Form inputs
│   ├── button.py            #   Button
│   ├── checkbox.py          #   Checkbox / CheckboxGroup
│   ├── chip.py              #   Chip
│   ├── color_picker.py      #   ColorPicker
│   ├── date_input.py        #   DateInput
│   ├── datetime_input.py    #   DateTimeInput
│   ├── file_picker.py       #   FilePicker / DirPicker
│   ├── image_picker.py      #   ImagePicker
│   ├── input.py             #   Input (data_type: str/int/float/file/dir)
│   ├── multi_select.py      #   MultiSelect
│   ├── radio.py             #   RadioGroup
│   ├── rating.py            #   Rating
│   ├── search_bar.py        #   SearchBar
│   ├── segmented_button.py  #   SegmentedButton
│   ├── select_box.py        #   SelectBox
│   ├── slider.py            #   Slider
│   └── switch.py            #   Switch
├── layout/                  # Layout & containers
│   ├── card.py              #   Card
│   ├── column.py            #   Column
│   ├── container.py         #   Container
│   ├── divider.py           #   Divider
│   ├── expander.py          #   Expander
│   ├── grid_view.py         #   GridView
│   ├── list_view.py         #   ListView
│   ├── page.py              #   PageLayout
│   ├── row.py               #   Row
│   ├── stack.py             #   Stack
│   ├── table.py             #   Table (with paging)
│   ├── tabs.py              #   Tabs / Tab / TabBar / TabBarView
│   ├── time_line.py         #   Timeline / TimelineItem
│   └── view.py              #   View
├── navigation/              # Navigation
│   ├── app_bar.py           #   AppBar
│   ├── bread_crumb.py       #   BreadCrumb / Crumb
│   └── paging.py            #   Paging / PagingState
└── utils/                   # Utilities
    ├── code_editor.py       #   Code
    ├── code_view.py         #   CodeView
    └── componts.py          #   Helper components

Component Overview

Category Components
Core ThemeProvider, ThemeSwitcher, use_theme, theme_state
Chart BarChart, LineChart, AreaChart, ScatterChart
Display Image, ImageGridView, ListTile, LogContainer, Text / Header_1..5 / Quote / Link / Code / Markdown / Json
Display / Media Audio, AudioPlayer, Video, Pdf
Feedback AlertDialog, Loading, Message, ProgressBar, Toast
Input Button, Checkbox / CheckboxGroup, Chip, ColorPicker, DateInput, DateTimeInput, FilePicker / DirPicker, ImagePicker, Input, MultiSelect, RadioGroup, Rating, SearchBar, SegmentedButton, SelectBox, Slider, Switch
Layout Card, Column, Container, Divider, Expander, GridView, ListView, PageLayout, Row, Stack, Table, Tabs, Timeline, View
Navigation AppBar, BreadCrumb, Paging

Two paradigms:

  • Static components (Button / Table / Rating / Timeline / charts …) subclass Flet controls and are ready to use.
  • Interactive components (Input / Checkbox / Switch / SelectBox / MultiSelect / Pdf …) are @ft.component functions; their state is declared with hooks and the return value goes straight into the tree.

Design Tokens and Theming

Components never contain raw color / radius / font-size literals — everything comes from ui.core.tokens. Switching theme and brand color is therefore a framework-level operation rather than per-widget recoloring.

Apply a theme

Wrap the root view in ThemeProvider; build returns your router:

import flet as ft
from ui import ThemeProvider

def App() -> ft.Control:
    return ThemeProvider(
        build=lambda: ft.Router(ROUTES, not_found=NotFoundPage, manage_views=False)
    )

def main(page: ft.Page):
    page.render(App)

ThemeProvider builds the subtree with use_memo(build, []) — switching the theme does not rebuild the widget tree, so values already typed into a form survive the switch (verified on a real device: after switching to dark, the use_state value is intact).

Light/dark and brand color

from ui import use_theme, ThemeSwitcher, PRIMARY_SEEDS

@ft.component
def TopBar() -> ft.Control:
    state = use_theme()                    # subscribe (must be called inside a component)
    ft.Button("toggle", on_click=lambda e: state.toggle_mode())
    ft.Button("recolor", on_click=lambda e: state.set_primary(PRIMARY_SEEDS["violet"]))

ThemeSwitcher is a ready-made top-bar control (brand palette + light/dark switch). ThemeState exposes three distinct behaviors:

Method Behavior
toggle_mode() always jump to the opposite of what is currently visible (for a ☀/🌙 button, so a click always shows a change)
cycle_mode() system → light → dark → system (for a dropdown)
set_mode(name) go straight to a mode; "system" follows the platform brightness
set_primary(hex) swap the brand seed; the whole M3 scheme is re-derived

Tokens

Theme-aware tokens are ft.Colors role names resolved by Flutter at paint time, so a theme switch costs zero Python re-renders:

ft.Colors.PRIMARY, ft.Colors.SURFACE_CONTAINER_LOWEST, ft.Colors.ON_SURFACE_VARIANT

Framework-resolved tokens cover the semantic roles M3 does not ship (ui.core.tokens):

from ui import SPACE, RADIUS, FONT_SIZE, GAP, PAD, elevation, overlay, hairline

SPACE["4"]          # 16 — a 4pt scale: (0,1,2,3,4,5,6,8,10,12,16,20,24,32,40)
RADIUS["lg"]        # 8
FONT_SIZE["body"]   # 13
elevation(2)        # five shadow levels, tinted with the 'shadow' role so it follows light/dark
overlay(0.06)       # 'onsurface,0.06' — a translucent scrim that adapts automatically
hairline()          # 1px divider

Semantic colors come in three tiers — one value cannot serve every use:

Function Use Light Dark
semantic("success") solid fill (badge background, status dot, progress segment) #047857 #34D399
on_semantic("success") foreground on that fill #FFFFFF #04231A
semantic_text("success") text / icons / hairlines on the page #047857 #34D399

Why: measured WCAG contrast, the previous #10B981 is only 2.54:1 on white and #F59E0B only 2.15:1 — white text on them is equally unreadable at 2.54 / 2.15. Each tier now clears 4.5:1, guarded by assertions in scripts/_theme_logic.py.

Available roles: success / warning / danger / info / neutral.

One important trap

In flet 1.0.5, reading a field of an observable does not subscribe to it. Subscription only happens in three places: the component argument is an Observable, use_state, or use_context.

state = theme_state()
mode = state.mode          # ❌ just a read; nothing re-renders
state = use_theme()        # ✅ use_context(theme_context); flet attaches the subscription

Relatedly, theme-aware role names and Python-computed colors have very different costs: role names are resolved by Flutter (free on theme switch), whereas a hex computed by semantic("success") only updates if the component itself subscribed to the theme.

Importing

All components can be imported directly from ui:

from ui import Button, Card, Column, Container, Divider, Row, Text, TextField

Since ui re-exports Flet, you can also access Flet types directly:

from ui import ft  # the flet module

# or
from ui import Page, Colors, Icons, MainAxisAlignment, CrossAxisAlignment

Examples

Run the demo to see every component in action (11 category pages plus a 404 page):

python examples/demo.py
Route Contents
/ Home navigation (enter each category)
/general Text / buttons / icons / chips
/layout Containers / lists / tables / timeline
/navigation Breadcrumb / tabs / paging
/form Inputs / selectors / slider / rating
/upload Files / directories / save / images
/feedback Toast / message / dialog / progress
/display Logs / code / images
/charts Line / area / bar / scatter charts
/media Audio / video / PDF
/about Migration cheat-sheet and design notes

Additional test files:

  • examples/test_home.py — minimal home page
  • examples/test_form.py — form components
  • examples/test_feedback.py — feedback (dialog, snackbar, loading, progress)
  • examples/test_chart.py — chart components
  • examples/test_line_chart.py — line chart
  • examples/test_button.py — button variants
  • examples/test_minimal.py / test_simple.py — minimal examples
  • examples/my_control.py — custom control example

MCP server (ui-mcp)

An MCP server that gives AI agents version-accurate knowledge of this component library — same idea as flet-mcp, but for ui. The index is built by runtime introspection (@ft.control flattens the constructor and @ft.observable creates proxy classes, so a static scan would miss the real signatures) and shipped as JSON; the server never imports ui.

pip install -e ".[mcp]"
ui-mcp            # stdio server (default subcommand)
ui-mcp build      # rebuild the index after changing src/ui
ui-mcp check      # is the index older than the sources?
ui-mcp guide      # list the curated "gotchas" topics

Client config:

{ "mcpServers": { "ui": { "command": "ui-mcp" } } }

12 tools in five groups (UI_MCP_ENABLE_{API,ICONS,EXAMPLES,DOCS,GUIDE}): get_api (primary verifier, walks base classes), list_components, search_components (Chinese + English), enum tools, find_icon, search_examples / get_example (real demo code, with per-component call sites), search_docs / get_doc, and get_guidelines — the paradigm rules that no API lookup can tell you (frozen trees, page.render, #AARRGGBB colors, design tokens).

Adding a component? Add one line to src/ui_mcp/aliases.py so Chinese queries find it, then run ui-mcp build.

Metadata

Release files for cs-ui 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cs-ui 0.1.1
File Size Uploaded Provenance Actions
cs_ui-0.1.1.tar.gz 338.2 kB Without provenance Details for cs_ui-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for cs-ui 0.1.1
File Interpreter ABI Platform Actions
cs_ui-0.1.1-py3-none-any.whl Python 3 none any Details for cs_ui-0.1.1-py3-none-any.whl

Total release size: 720.9 kB

Release files / cs_ui-0.1.1.tar.gz

Download URL cs_ui-0.1.1.tar.gz
Size 338.2 kB
Tags Source
SHA-256 checksum
How to use checksums
b919c270a7f920224180d705ae1e4e0fa64b64db42d5d66e5ae2a1f1392474e4
BLAKE2b-256 checksum
How to use checksums
972233a489438dedd2dda1f0f27107bcf3952152d09fb870c304d558a80027be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release files / cs_ui-0.1.1-py3-none-any.whl

Download URL cs_ui-0.1.1-py3-none-any.whl
Size 382.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d464610dcc7bd240fb0cd5e6e4f5e1f166c8994857481d4aae4b6c7fbe11d73a
BLAKE2b-256 checksum
How to use checksums
bb2af1f2b8762c977edeea94d3d39b521dd4fafa52523de173c93c9722b8efae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Security for cs-ui 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page