Skip to main content

Dars is a Full-Stack multiplatform Python UI framework for building modern, interactive web and desktop apps with Python code. Seamlessly integrated with FastAPI, it allows you to build complete applications with Server-Side Rendering (SSR) and reactive SPA capabilities also classic multipage html and desktop apps in a single codebase.

Project description

Dars Framework

Dars Framework Logo

PyPI Version Python Versions License Ask DeepWiki

Dars is a Full-Stack multiplatform Python UI framework for building modern, interactive web and desktop apps entirely in Python. Seamlessly integrated with FastAPI, it lets you build complete applications with Server-Side Rendering (SSR), reactive SPA routing, static site generation, and a production-ready backend API — all from a single Python codebase, with zero JavaScript required.

Official Website | Documentation Docs | Official Roadmap | Extension for VSCode here and OpenVSX version here

pip install dars-framework

Try Dars without installing anything — visit the Dars Playground


How It Works

  • Build your UI using Python classes and components (Text, Button, Container, Page, Each, Show, If, etc.).
  • Preview instantly with hot-reload using app.rTimeCompile().
  • Export your app to static/dynamic/SSR web files with a single CLI command.
  • Export to native desktop apps (BETA) using project config format: "desktop" and dars build.
  • Use multipage layouts, scripts, hooks, and more — see docs for advanced features.
  • One app, four deployment targets simultaneously: Dars supports Static Site Generation (SSG), Single-Page Application (SPA) routing, Server-Side Rendering (SSR) with FastAPI, and a full Backend API — all from the same Python codebase. Mix and match freely: export some pages as static HTML for SEO, serve others via SSR for dynamic content, and expose REST API endpoints alongside your UI.
  • Full backend toolkit included: useFetch for declarative data fetching, FormValidator for client-side validation, Each for runtime list rendering from API responses, JsonStore for file-backed persistence, UploadPipeline for secure file uploads, SecurityHeadersMiddleware for HTTP security, and DarsEnv for .env file support.
  • For more information visit the Documentation

Quick Example: Your First App

from dars.all import *

app = App(title="Hello World", theme="dark")

# 1. Define State
state = State("app", title_val="Simple Counter", count=0)

# 2. Define Route
@route("/")
def index():
    return Page(
        Text(
            text=useValue("app.title_val"),
            style="fs-[33px] text-black font-bold mb-[5px]",
        ),
        Text(
            text=useDynamic("app.count"),
            style="fs-[48px] mt-5 mb-[12px]"
        ),
        Button(
            text="+1",
            on_click=state.count.increment(1),
            style="bg-[#3498db] text-white p-[15px] px-[30px] rounded-[8px] cursor-pointer fs-[18px]",
        ),
        Button(
            text="-1",
            on_click=state.count.decrement(1),
            style="bg-[#3498db] text-white p-[15px] px-[30px] rounded-[8px] cursor-pointer fs-[18px] mt-[5px]",
        ),
        Button(
            text="Reset",
            on_click=state.reset(),
            style="bg-[#3498db] text-white p-[15px] px-[30px] rounded-[8px] cursor-pointer fs-[18px] mt-[5px]",
        ),
        style="flex flex-col items-center justify-center h-[100vh] ffam-[Arial] bg-[#f0f2f5]",
    )

app.add_page("index", index(), title="index")

if __name__ == "__main__":
    app.rTimeCompile()

State Management System

Dars features a powerful state management system designed for different use cases.

Hook-Based State Management

Modern, Pythonic state management for reactive updates. Best for counters, timers, and component interactions.

Hooks System:

  • useDynamic(): Reactive state binding for automatic UI updates.
  • useValue(): Set initial values from state (non-reactive).
  • useWatch(): Monitor state changes and trigger side effects.

Learn more about Hooks

from dars.all import *

app = App("State Demo")
state = State("counter", count=0)

@route("/")
def index():
    return Page(
        Text(text=useDynamic("counter.count"), style="fs-[24px]"),
        Button("Increment", on_click=state.count.increment(1)),
        Button("Decrement", on_click=state.count.decrement(1)),
        Button("Reset",     on_click=state.count.set(0)),
    )

app.add_page("index", index())

if __name__ == "__main__":
    app.rTimeCompile()

[!WARNING] Important: When using hooks, the State ID is used for binding. Do not use an ID that belongs to another unrelated component, as hooks use this ID as the State ID. Using a conflicting ID may cause unexpected behavior or state collisions.

For detailed documentation, visit the State Management Guide.


Animation System

Dars includes 15+ built-in animations that work seamlessly with state management:

from dars.all import fadeIn, fadeOut, pulse, shake, sequence

# Single animation
button.on_click = fadeIn(id="modal", duration=500)

# Chain multiple animations
button.on_click = sequence(
    fadeIn(id="box"),
    pulse(id="box", scale=1.2, iterations=2),
    shake(id="box", intensity=5)
)

Available Animations: fadeIn, fadeOut, slideIn, slideOut, scaleIn, scaleOut, pulse, shake, bounce, rotate, flip, colorChange, morphSize, popIn, popOut

For complete animation documentation, visit the Animation Guide.


Routing System (SPA & SSR)

Dars offers a flexible routing system supporting Client-Side Routing (SPA), Server-Side Rendering (SSR), and Static Site Generation — all in one app.

Server-Side Rendering (SSR)

from dars.all import *

@route("/dashboard", route_type=RouteType.SSR)
def dashboard():
    return Page(
        Text("Server-Side Rendered Page"),
        Button("Click Me", on_click=alert("Hello from Client")),
    )

Client-Side Routing (SPA)

@route("/")
def home():
    return Page(Text("Home Page"))

@route("/about")
def about():
    return Page(Text("About Us"))

Nested Routes with Outlet

@route("/dashboard")
def dashboard():
    return Page(
        Text("Dashboard", style="fs-[24px]"),
        Container(
            Link("Settings", href="/dashboard/settings"),
            Link("Profile",  href="/dashboard/profile"),
        ),
        Outlet(),
    )

app.add_page("dashboard", dashboard(), index=True)
app.add_page("settings", settings_page, route="/dashboard/settings", parent="dashboard")
app.add_page("profile",  profile_page,  route="/dashboard/profile",  parent="dashboard")

404 Error Handling

app.set_404_page(Page(
    Text("Page not found", style="fs-[32px] text-red-500"),
    Link("Go Home", href="/"),
))

Custom Components

Function Components

from dars.all import *

@FunctionComponent
def UserCard(name, email, **props):
    return f"""
    <div {Props.id} {Props.class_name} {Props.style}>
        <h3>{name}</h3>
        <p>{email}</p>
        <div class="card-body">{Props.children}</div>
    </div>
    """

card = UserCard("John Doe", "john@example.com", id="user-1", style="p-[20px]")

Control Flow Components

Show — Runtime Visibility Toggle

Always renders children into the DOM. A falsy VRef condition hides the wrapper with display:none. Reacts to VRef changes at runtime — perfect for loading spinners, error banners, and any UI that toggles based on live state.

from dars.all import *

is_loading = setVRef(True,  ".loading")
has_error  = setVRef(False, ".error")

Show(
    is_loading,
    Container(Spinner(), Text("Loading…"), style="flex items-center gap-2"),
)

Show(
    has_error,
    Container(
        Text("Something went wrong.", style="text-red-600"),
        style="bg-red-50 border rounded p-3",
    ),
)

Each — List Rendering from Python or API

Renders a list using a template function. Works with both compile-time Python lists and runtime VRef arrays (e.g. from useFetch).

Compile-time list:

users = [{"name": "Alice"}, {"name": "Bob"}]

Each(items=users, render=lambda u: Text(u["name"]))

Runtime VRef list (from API):

The render function is called at export time with a sentinel dict containing __item_<field>__ placeholders. At runtime, dom_each_render substitutes real values for each item. Use __item_done_class__ to get "line-through text-gray-400" for done items automatically.

tasks_vref = setVRef([], ".tasks-data")  # filled by useFetch

def task_item(t):
    title   = t.get("title", "__item_title__") if isinstance(t, dict) else "__item_title__"
    item_id = t.get("id",    "__item_id__")    if isinstance(t, dict) else "__item_id__"
    return Container(
        Text(title,         style="flex: 1 1 0%", class_name="__item_done_class__"),
        Text(f"#{item_id}", style="text-xs text-gray-400 ml-2"),
        style="flex items-center gap-2 p-2 border rounded bg-white shadow-sm",
    )

Each(items=tasks_vref, render=task_item, class_name="space-y-1 mb-6")

Supported API response shapes are automatically unwrapped: {"tasks":[...]}, {"items":[...]}, {"data":[...]}, {"results":[...]}, or a plain [...] array.


Backend & API Communication

Dars ships a complete production-grade backend toolkit. Everything is pure Python — no JavaScript required.

useFetch — Declarative Data Fetching

Fetch data from any API and bind the response to reactive VRefs automatically:

from dars.all import *

tasks_sel = ".tasks-data"
_tasks    = setVRef([], tasks_sel)

trigger, loading, data, error = useFetch(
    "/api/tasks",
    on_success=runSequence(
        updateVRef(".loading", False),
        updateVRefFromResponse(tasks_sel),  # stores API response → VRef
    ),
    on_error=runSequence(
        updateVRef(".loading", False),
        updateVRef(".error", True),
    ),
)

page = Page(
    Show(loading, Spinner()),
    Show(error,   Text("Could not load tasks.", style="text-red-500")),
    Each(items=_tasks, render=lambda t: Container(
        Text(t.get("title", "__item_title__"), style="flex-1"),
        Text(f"#{t.get('id', '__item_id__')}",  style="text-xs text-gray-400"),
        style="flex gap-2 p-2 border rounded bg-white",
    )),
    Button("↻ Refresh", on_click=trigger),
)
page.add_script(trigger)  # auto-run on page load

FormValidator — Client-Side Validation

Validate forms before submitting — rules declared once in Python, enforced both client-side and server-side:

validator = FormValidator({
    "email":    [required(), email()],
    "password": [required(), min_length(8)],
})

submit = validator.validated_submit(
    url="/api/login",
    form_data=collect_form(email=V("#email"), password=V("#password")),
    on_success=goTo("/dashboard"),
    on_error=setText("error-msg", "Login failed."),
)

page = Page(
    Input(id="email",    placeholder="Email"),
    Text("", id="email-error",    style="text-red-500 text-sm"),
    Input(id="password", placeholder="Password"),
    Text("", id="password-error", style="text-red-500 text-sm"),
    Text("", id="error-msg",      style="text-red-500 text-sm"),
    Button("Login", on_click=submit),
)

JsonStore — File-Backed Persistence

Thread-safe, atomic-write JSON storage for rapid prototyping and small backends:

from dars.backend.store import JsonStore

store = JsonStore("tasks.json", default={"tasks": []})

tasks = store.get("tasks", [])
tasks.append({"id": 1, "title": "New task", "done": False})
store.set("tasks", tasks)

UploadPipeline — Secure File Uploads

from dars.backend.upload import UploadPipeline

pipeline = UploadPipeline(
    upload_dir="uploads",
    allowed_types=["image/png", "image/jpeg", "application/pdf"],
    max_size_bytes=10 * 1024 * 1024,
)
pipeline.create_endpoint(app, path="/api/upload")

Frontend component:

FileUpload(
    upload_url="/api/upload",
    accepted_types=["image/png", "image/jpeg"],
    max_size_bytes=5 * 1024 * 1024,
    on_upload_complete=setText("status", "Uploaded!"),
    on_upload_error=setText("status", "Upload failed."),
)

SecurityHeadersMiddleware — HTTP Security

from dars.backend.ssr import SSRApp

ssr = SSRApp(dars_app, prefix="/api/ssr")
ssr.use_cors(origins=["http://localhost:4000"], credentials=True)
ssr.use_security_headers()  # X-Frame-Options, CSP, HSTS, etc.
ssr.use_upload(upload_dir="uploads", allowed_types=["image/*"])

app = ssr.fastapi_app

DarsEnv — Environment Variables

from dars.env import DarsEnv

DarsEnv.load()                          # loads .env silently if present
api_key = DarsEnv.get("API_KEY")        # None if missing
secret   = DarsEnv.require("SECRET")   # raises KeyError if missing

Full Backend API Example

from dars.backend.ssr import SSRApp
from dars.backend.store import JsonStore
from main import app as dars_app
from fastapi import Request
from fastapi.responses import JSONResponse
import os

ssr = SSRApp(dars_app, prefix="/api/ssr")
ssr.use_cors(origins=["http://localhost:4000"], credentials=True)
ssr.use_security_headers()

app = ssr.fastapi_app

# ── Production: serve dist/ as static files with SPA fallback ───────────────
from backend.apiConfig import DarsEnv
if not DarsEnv.is_dev():
    ssr.use_spa_fallback()
# ────────────────────────────────────────────────────────────────────────────

_store = JsonStore("tasks_db.json", default={"tasks": []})

@app.get("/api/tasks")
async def get_tasks():
    return JSONResponse({"tasks": _store.get("tasks", [])})

@app.post("/api/tasks")
async def create_task(request: Request):
    body  = await request.json()
    tasks = _store.get("tasks", [])
    task  = {"id": len(tasks) + 1, "title": body.get("title", ""), "done": False}
    tasks.append(task)
    _store.set("tasks", tasks)
    return JSONResponse(task, status_code=201)

@app.put("/api/tasks/{task_id}")
async def update_task(task_id: int, request: Request):
    body  = await request.json()
    tasks = _store.get("tasks", [])
    for t in tasks:
        if t["id"] == task_id:
            t.update({k: v for k, v in body.items() if k != "id"})
            _store.set("tasks", tasks)
            return JSONResponse(t)
    return JSONResponse({"error": "Not found"}, status_code=404)

@app.delete("/api/tasks/{task_id}")
async def delete_task(task_id: int):
    tasks = [t for t in _store.get("tasks", []) if t["id"] != task_id]
    _store.set("tasks", tasks)
    return JSONResponse({"deleted": task_id})

if __name__ == "__main__":
    import uvicorn
    print("\n" + "=" * 60)
    print("Dars Fullstack Backend")
    print("=" * 60)
    if DarsEnv.is_dev():
        port, host = 3000, "127.0.0.1"
    else:
        port, host = 8000, "0.0.0.0"
    print("=" * 60 + "\n")
    uvicorn.run(app, host=host, port=port)

For complete backend documentation, see the Backend & API Guide.


CLI Usage

Command What it does
dars export my_app.py --format html Export app to HTML/CSS/JS in ./my_app_web
dars init --type desktop Scaffold desktop-capable project (BETA)
dars init --type fullstack Scaffold full-stack project (SPA + SSR + API)
dars build (desktop config) Build desktop app artifacts (BETA)
dars preview Preview exported app (auto-detects output)
dars preview --port 9000 Preview on a custom port
dars init my_project Create a new Dars project
dars init --update Create/Update dars.config.json in current dir
dars build Build using dars.config.json
dars config validate Validate dars.config.json and print report
dars info my_app.py Show info about your app
dars formats List supported export formats
dars dev Run the configured entry file with hot preview
dars dev --port 9000 Run dev server on a custom port
dars dev --backend Run only the configured backendEntry (FastAPI/SSR backend)
dars generate component <name> Scaffold a new FunctionComponent
dars generate page <name> Scaffold a new page (static, SPA, or SSR)
dars --help Show help and all CLI options

Tip: use dars doctor to review optional tooling that can enhance bundling/minification.

Running with Backend

# Terminal 1 — Frontend dev server
dars dev

# Terminal 2 — Backend SSR + API server
dars dev --backend

Local Execution and Live Preview

if __name__ == "__main__":
    app.rTimeCompile()
python my_app.py
# → http://localhost:8000

python my_app.py --port 8088
# Auto-detects output directory and port from config
dars preview

# Or specify a custom path and port
dars preview ./my_exported_app -p 8080

Project Configuration (dars.config.json)

{
  "entry": "main.py",
  "format": "html",
  "outdir": "dist",
  "publicDir": null,
  "include": [],
  "exclude": ["**/__pycache__", ".git", ".venv", "node_modules"],
  "bundle": true,
  "defaultMinify": true,
  "viteMinify": true,
  "utility_styles": {},
  "markdownHighlight": true,
  "markdownHighlightTheme": "auto",
  "port": 8000,
  "backendEntry": "backend.api:app"
}
Key Description
entry Python entry file for dars build and dars export config
format Export format: html or desktop
outdir Output directory
publicDir Folder copied into output (auto-detected if null)
bundle Reserved for future use
defaultMinify Toggle Python HTML/JS/CSS minifier (default true)
viteMinify Toggle Vite/esbuild minifier for JS/CSS (default true)
utility_styles Custom utility class definitions
markdownHighlight Auto-inject Prism.js for Markdown code blocks (default true)
backendEntry Python import path for SSR/backend app (e.g. "backend.api:app")
port Dev preview server port (default 8000)
dars config validate
dars build

Documentation


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

dars_framework-1.9.11.tar.gz (991.4 kB view details)

Uploaded Source

Built Distribution

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

dars_framework-1.9.11-py3-none-any.whl (923.7 kB view details)

Uploaded Python 3

File details

Details for the file dars_framework-1.9.11.tar.gz.

File metadata

  • Download URL: dars_framework-1.9.11.tar.gz
  • Upload date:
  • Size: 991.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for dars_framework-1.9.11.tar.gz
Algorithm Hash digest
SHA256 1382255312b13855e6f82765c9a64fe932edb768dec62a7e6609c4b0d7cbb9ea
MD5 4a49fe9291d494225ec70627f8bc9a8d
BLAKE2b-256 a334ac87f737191c4df8a14b7043d0659dbb6ec2d64dbffd668f3f47a9373b8f

See more details on using hashes here.

File details

Details for the file dars_framework-1.9.11-py3-none-any.whl.

File metadata

File hashes

Hashes for dars_framework-1.9.11-py3-none-any.whl
Algorithm Hash digest
SHA256 fededdbae5b75316d10366c7ce93b0bca965d0e8ed08ddee925b19abfe9681f5
MD5 65c3b62ec9c82d5f396dfea743e99027
BLAKE2b-256 b610036cc86ffddbd47bc04a6a2cfb6fdee0fd1e8e4eae45cd9fc96619ebcc70

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