Dars Framework Logo
Documentation Everything you need to build modern apps with Python.

Dars Framework Documentation

Dars Framework is a full-stack Python UI framework for building web applications. It provides a declarative component-based architecture where developers define UIs in Python, which are then compiled to optimized HTML, CSS, and JavaScript. The framework supports multiple deployment targets from a single codebase: Single-Page Applications (SPA), Multi-Page Applications (MPA), and Server-Side Rendered (SSR) applications with FastAPI.


Why use Dars Framework?

  • Python-Native : Write your entire application, from UI to backend logic, using 100% Python. No need to switch between multiple languages.
  • Modern Styling System : Built-in Tailwind-like utility system that compiles directly to optimized CSS without external build tools like Node.js or PostCSS.
  • Full-Stack by Design : Seamlessly transition from static SPAs to complex SSR applications with FastAPI integration.
  • Declarative & Reactive : Build complex UIs with ease using a component-based architecture and a powerful reactive state management system (Hooks).
  • Multi-Target Deployment : One codebase for Web (SPA/SSR).
  • SEO Ready : Advanced SSR and Metadata (Head) management ensure your application is fully discoverable by search engines.
  • Developer Experience : Fast feedback loop with a powerful CLI, hot reloading, and zero-config deployment options.

Getting Started

If you are new to Dars, we recommend following the learning path in this order:

1. Installation & Setup

  • Installation Guide : Get Dars running on your machine and set up the VS Code extension.
  • Quick Start : Create your first "Hello World" application in minutes.
  • CLI Reference : Learn how to use the dars command to init, build, and export projects.

2. Core Concepts

3. Advanced Features

4. Specialized Topics

  • Animations : Add smooth transitions and interactive animations to your UI.
  • Key Events : Handle global and component-level keyboard shortcuts.
  • Exporters : Understand how your code is compiled for different platforms.
  • Project Config : Deep dive into dars.config.json and environmental variables.

Community & Support

Installation Guide

Quick Installation

To install Dars, simply use pip:

pip install dars-framework

This will install Dars and all its dependencies automatically.

VS Code Extension

You can install the official Dars Framework VS Code extension to have the dars dev tools.

  • VS Code Marketplace : https://marketplace.visualstudio.com/items?itemName=ZtaMDev.dars-framework
  • Open VSX : https://open-vsx.org/extension/ztamdev/dars-framework

CLI Usage

Once installed, the dars command will be available in your terminal. You can use it to:

Export Applications

dars export my_app.py --format html --output ./my_app_web

Preview Applications

dars preview ./my_app_web

Initialize a New Project

# Basic project with Hello World
dars init my_new_project

# Project with a specific template
dars init my_project -t basic/Forms

View Application Information

dars info my_app.py

View Supported Formats

dars formats

Post-Installation Verification

To verify that Dars has been installed correctly, open your terminal and run:

dars --help

You should see the help for the dars command, indicating that the installation was successful.

First Steps After Installation

1. Create Your First Application (my_first_app.py)

from dars.core.app import App
from dars.components.basic.text import Text
from dars.components.basic.container import Container

app = App(title="My First App")
container = Container(style="p-5") # Use ' to escape quotes
text = Text(text="Hello Dars!", style="text-2xl") # Use ' to escape quotes

container.add_child(text)
app.set_root(container)

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

2. Export the Application

Save the code above as my_first_app.py and then run:

dars export my_first_app.py --format html --output ./my_app

3. Preview

dars preview ./my_app

Useful Commands

# General help
dars --help

# Application information
dars info my_app.py

# Available formats
dars formats

# Preview application
dars preview ./output_directory

Post-Installation Checklist

  • [x] Python 3.8+ installed
  • [x] Dars Framework installed via pip install dars-framework
  • [x] CLI dars works correctly ( dars --help )
  • [x] Basic test run successfully
  • [x] Example exported and previewed correctly
  • [x] Documentation reviewed

Congratulations! Dars is ready to use.


Web Export

Dars exports web applications as optimized HTML, CSS, and JavaScript. Run dars build to generate production-ready web assets in your configured output directory.

dars build

Notes:

  • Ensure your dars.config.json is configured correctly for web export.
  • Use dars preview to inspect the generated output locally.

Getting Started with Dars

Welcome to Dars, a modern Python framework for building web applications with reusable UI components.

VS Code Extension

There is an official Dars Framework extension for VS Code to have the dars dev tools.

  • VS Code Marketplace : https://marketplace.visualstudio.com/items?itemName=ZtaMDev.dars-framework
  • Open VSX : https://open-vsx.org/extension/ztamdev/dars-framework

Quick Start

  1. Install Dars
    See INSTALL section for installation instructions.

  2. Explore Components
    Discover all available UI components in components.md .

  3. Command-Line Usage
    Find CLI commands, options, and workflows in cli.md .

  4. App Class Learn how to create an app class in App Documentation .

  5. Component Search and Modification All components in Dars now support a powerful search and modification system:

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(
        # 3. Use useValue for app text
        Text(
            text=useValue("app.title_val"),
            style="fs-[33px] text-black font-bold mb-[5x] ",
        ),

        # 4. Display reactive count
        Text(
            text=useDynamic("app.count"),
            style="fs-[48px] mt-5 mb-[12px]"
        ),
        # 5. Interactive Button
        Button(
            text="+1",
            on_click=(
                state.count.increment(1)
            ),
            style="bg-[#3498db] text-white p-[15px] px-[30px] rounded-[8px] border-none cursor-pointer fs-[18px]",
        ),

        # 6. Interactive Button
        Button(
            text="-1",
            on_click=(
                state.count.decrement(1)
            ),
            style="bg-[#3498db] text-white p-[15px] px-[30px] rounded-[8px] border-none cursor-pointer fs-[18px] mt-[5px]",
        ),
        # 7. Interactive Button
        Button(
            text="Reset",
            on_click=(
                state.reset()
            ),
            style="bg-[#3498db] text-white p-[15px] px-[30px] rounded-[8px] border-none cursor-pointer fs-[18px] mt-[5px]",
        ),
        style="flex flex-col items-center justify-center h-[100vh] ffam-[Arial] bg-[#f0f2f5]",

    )

# 8. Add page
app.add_page("index", index(), title="index")

if __name__ == "__main__":
    app.rTimeCompile(add_file_types=".js,.css")
  • Include any extension your project uses beyond default Python files.

Need More Help?

  • For advanced topics, see the full documentation and examples in the referenced files above.
  • If you have questions or need support, check the official repository or community channels.

Start building with Dars...

Dars CLI Reference

The Dars Command Line Interface (CLI) lets you manage your projects, export apps, and preview results quickly from the terminal.

How to Use the CLI

Open your terminal in your project directory and use any of the following commands:

# Show information about your app
 dars info my_app.py

# List supported export formats
 dars formats

# Initialize a new project (Default is SPA)
 dars init my_new_project

# Initialize a Full-Stack SSR project
 dars init my_new_project --type ssr

# Initialize a project with a specific template
 dars init my_new_project -t demo/complete_app

# Preview an exported app
 dars preview ./output_directory
 # New in v1.9.6: Path is optional (defaults to config outdir or ./dist)
 dars preview
 # New in v1.9.6: Custom port support
 dars preview -p 9000

# Build using project config (dars.config.json)
 dars build
 # Build without the default Python minifier
 dars build --no-minify

# Help
 dars --help

# Version
 dars -v

Code Generation (New in v1.9.6)

Scaffold new components and pages instantly with automatic project integration.

# Generate a new FunctionComponent
 dars generate component MyComponent
 dars g component MyComponent

# Generate a new Page
 dars generate page About
 # Specify page type directly
 dars generate page About --page-type spa
 dars generate page Home --page-type static
 dars generate page Profile --page-type ssr

# Generate with auto-injection to main.py
 dars generate page Contact -y

Main Commands Table

Command What it does
dars export my_app.py --format html Export app to HTML/CSS/JS in ./my_app_web
dars export my_app.py --format html --no-minify Export skipping default Python minifier
dars preview Preview exported app (auto-detects output)
dars preview --port 9000 Preview on a custom port
dars build Build using dars.config.json
dars build --no-minify Build skipping default Python minifier
dars init my_project --type ssr Create a new Full-Stack SSR project
dars init my_project Create a new Dars project (SPA Default)
dars dev Run the configured entry file with hot preview (app.rTimeCompile)
dars dev --port 9000 Run the dev server on a custom port
dars dev --backend Deprecated: use dars dev with backendEntry configured.
dars info my_app.py Show info about your app
dars formats List supported export formats
dars generate component <name> Scaffold a new FunctionComponent
dars generate page <name> Scaffold a new page (static or SPA)
dars generate page <name> -y Scaffold and auto-inject into main app
dars --help Show help and all CLI options

Using Official Templates

Dars provides official templates to help you start new projects quickly. Templates include ready-to-use apps for forms, layouts, dashboards, multipage, and more.

How to Use a Template

  1. Initialize a new project with a template:
    dars init my_new_project -t basic/HelloWorld
    # ...and more (see below)
    

You can see the templates available with

dars init --list-templates
dars init  -L
  1. Export the template to HTML/CSS/JS:

    dars export {file.py} --format html --output ./hello_output
    dars export {file.py} --format html --output ./dashboard_output
    # ...etc
    
  2. Preview the exported app:

    dars preview ./hello_output
    

Tips CLI

  • Use dars --help for a full list of commands and options.
  • You can preview apps either live (with app.rTimeCompile() ) or from exported files with dars preview .
  • Templates are available for quick project setup: use dars init my_project -t <template> .

Minification labels in output

  • Applying minification (default): default Python-side minifier is active.
  • Applying minification (vite): Vite/esbuild minification is active (JS/CSS) and default is disabled.
  • Applying minification (default + vite): both are active.

For more, see the Getting Started guide and the main documentation index.

SSR Workflow (Full-Stack)

When working with SSR ( dars init --type ssr ), the workflow involves two processes:

  1. Frontend ( dars dev ) : Runs the Dars preview server on port 8000 (or the one set in dars.config.json ) using app.rTimeCompile() .
  2. Backend ( dars dev --backend ) : Deprecated. Use dars dev instead when backendEntry is configured; it starts frontend and backend together.

Common Commands:

Command Description
dars init --type ssr Scaffolds a project with backend/ folder and SSR config.
dars dev Starts the hot-reload frontend preview server.
dars dev --backend Deprecated: use dars dev with backendEntry configured.
dars build Builds static assets to dist/ for production.

Note: For production, you only need to run the backend (which serves the built assets).

Dars Project Configuration

The dars.config.json file is the central nervous system of your Dars project. It defines how your application is compiled, optimized, and prepared for deployment.


Configuration Overview

A standard configuration file looks like this:

{
  "entry": "main.py",
  "format": "html",
  "outdir": "dist",
  "publicDir": "public",
  "include": [],
  "exclude": ["**/__pycache__", ".git", ".venv", "node_modules"],
  "bundle": true,
  "minify": true,
  "markdownHighlight": true,
  "markdownHighlightTheme": "auto",
  "port": 8000,
  "utility_styles": {},
  "backendEntry": "backend.api:app"
}

Core Fields

1. Build & Path Configuration

  • entry (string): The Python entry point of your application. Defaults to main.py .
  • format (string): Target deployment format.
    • html : Standard web application (SPA/MPA/SSR).
  • outdir (string): The directory where compiled assets will be saved. Defaults to dist . This directory is also the default path used by dars preview .
  • publicDir (string): Directory for static assets (images, fonts, etc.) that will be copied directly to the output.

2. File Filtering

  • include (list): List of glob patterns or substrings to include from the public directory.
  • exclude (list): List of patterns to ignore during the build process.

3. Optimization & Minification

Dars now uses dars-bundler — a standalone, high-performance Rust binary — as its sole minification engine. It replaces the previous Python-based rjsmin/rcssmin pipeline and the optional Vite/esbuild dependency.

  • minify (boolean, default true ): Enables or disables JS and CSS minification via dars-bundler . When enabled, the bundler runs SWC (Rust) for AST-level JS minification with dead-code elimination, and LightningCSS for CSS minification. No Node.js, npm, or Vite installation is required.
  • bundle (boolean): Ensures all internal dependencies are bundled into the final distribution.

Note: The old viteMinify and defaultMinify keys are still accepted for backwards compatibility but are ignored — minify is the single control point.

4. Features & Integrations

  • markdownHighlight (boolean): Automatically injects Prism.js for syntax highlighting in Markdown components.
  • backendEntry (string): Import path for your FastAPI backend (e.g., "backend.api:app" ). Required for SSR projects.
  • port (number): The port for the development preview server. Defaults to 8000 . This port is respected by both dars dev and dars preview .

The dars-bundler

dars-bundler is a standalone Rust binary that Dars ships alongside its runtime. It is automatically detected and invoked during dars build / dars export no manual configuration is needed .

How it works

  1. After Dars generates your HTML/CSS/JS output, dars-bundler is called on the output directory.
  2. It walks all JS files and runs SWC minification (variable renaming, dead-code elimination, constant folding).
  3. It walks all CSS files and runs LightningCSS minification (vendor-prefix removal, color optimization, unused selector pruning).
  4. Files in lib/ (the Dars runtime: dars.min.js , dap.js , etc.) are processed with the same pipeline.
  5. HTML files are not touched by dars-bundler; they are already optimized by BeautifulSoup during export.

Discovery order

The bundler is resolved in this order:

  1. DARS_BUNDLER_PATH env variable (user override)
  2. Same directory as the Python executable
  3. System PATH
  4. dars/bundler/ subdirectory shipped with the framework
  5. DarsBundler dev repo target/debug or target/release (development mode)

Disabling minification

Set "minify": false in dars.config.json or pass --no-minify to the CLI:

dars build --no-minify
dars export --no-minify

Custom Utility Styles

One of Dars' most powerful features is the ability to define custom utility classes directly in the configuration. This allows you to create reusable design tokens.

{
  "utility_styles": {
    "btn-primary": [
      "bg-blue-600",
      "text-white",
      "px-4",
      "py-2",
      "rounded-lg",
      "hover:bg-blue-700",
      "transition-all"
    ],
    "card-glass": [
      "bg-white/30",
      "backdrop-blur-md",
      "border",
      "border-white/20",
      "rounded-2xl",
      "shadow-xl"
    ]
  }
}

Usage in Python:

Button("Click Me", style="btn-primary")
Container(style="card-glass")

Deployment Targets

Web (HTML)

The standard format for deploying to the web. When format is html , Dars generates optimized HTML, CSS, and JS files compatible with any static host or FastAPI server.


Best Practices

  1. Keep it minimal : Only include necessary files in your publicDir to speed up build times.
  2. Environment Variables : Use the dars env system to manage different configurations for development and production.
  3. Validate often : Use dars config validate to ensure your configuration matches the requirements of your chosen route types (especially for SSR).

Environment Management (DarsEnv)

Dars provides a simple, built-in way to detect the current running environment (Development vs Production) through the DarsEnv class.

Why use DarsEnv?

It is common to have logic that should only run during development (like showing debug tools, detailed logs, or specific navigation links) or only in production (like analytics scripts).

DarsEnv Class

The DarsEnv class is available directly from dars.env :

from dars.env import DarsEnv

Properties

DarsEnv.dev

A boolean property that indicates if the application is running in development mode.

  • Returns True : When running via dars dev (where bundling is disabled by default).
  • Returns False : When running via dars build or dars export (production builds).

Examples

Conditional Rendering

You can use DarsEnv.dev to conditionally render components:

from dars.all import *
from dars.env import DarsEnv

def MyPage():
    return Page(
        Container(
            Text("Welcome to My App"),

            # This link only appears in development
            Link("/debug-dashboard", "Debug Tools") if DarsEnv.dev else None,

            # Different footer for environments
            Text("Dev Mode: Active" if DarsEnv.dev else "Production Build")
        )
    )

Conditional Configuration

You can also use it to toggle configuration values:

api_url = "http://localhost:3000" if DarsEnv.dev else "https://api.myapp.com"

Note : DarsEnv is initialized automatically by the Dars CLI tools. You do not need to configure it manually.

App Class and PWA Features in Dars Framework

Overview

The App class is the core of any Dars Framework application. It represents the complete application and manages all configuration, components, pages, and functionalities, including Progressive Web App (PWA) support.

Basic Structure

class App:
    def __init__(
        self, 
        title: str = "Dars App",
        description: str = "",
        author: str = "",
        keywords: List[str] = None,
        language: str = "en",
        favicon: str = "",
        icon: str = "",
        apple_touch_icon: str = "",
        manifest: str = "",
        theme_color: str = "#000000",
        background_color: str = "#ffffff",
        service_worker_path: str = "",
        service_worker_enabled: bool = False,
        **config
    ):

Compile-Time Component Manipulation (App.create / App.delete)

Dars lets you modify the component tree before export or preview. Use these methods on App to insert or remove components safely at compile time.

App.create(target, root=None, on_top_of=None, on_bottom_of=None)

  • Purpose : Insert a component into the tree.
  • target can be:
    • A component instance (e.g., Button("OK", id="ok") ).
    • A callable that returns an instance (called lazily).
    • A str id of an existing component in the app tree to move it.
  • root : Where to insert. Accepts:
    • A component instance.
    • A component id ( str ).
    • A page name ( str ) in multipage apps.
  • on_top_of / on_bottom_of : Reference sibling inside root to place the new node before/after. Can be an id ( str ) or a component instance found within root (deep search). If neither is provided, it appends to root .
  • Works with both single-page ( app.root ) and multipage ( app.add_page ) setups.
  • If anything can’t be resolved, it fails gracefully without crashing.

Examples

from dars.all import *

app = App(title="Compile-time create/delete")

# Single-page usage
root = Container(Text("A" , id="a"), Text("C", id="c"), id="root")
app.set_root(root)

# Insert new Text before id="c"
app.create(Text("B", id="b"), root="root", on_top_of="c")

# Move existing component by id to bottom
app.create("a", root="root", on_bottom_of="c")

# Multipage usage (root by page name)
home = Page(Container(Text("Home"), id="home_root"))
app.add_page(name="home", root=home, index=True)
app.create(Text("Welcome", id="welcome"), root="home", on_bottom_of="home_root")

App.delete(id)

  • Purpose : Remove a component from the tree by its id .
  • If the id does not exist, it becomes a no-op (safe warning behavior).
# Remove a component by id before export/preview
app.delete("b")

These operations run before export and affect the generated HTML/VDOM. For dynamic changes at runtime in the browser, see the runtime APIs in the Components documentation.

Global Styles Management

Enhanced add_global_style() Method

The App class now includes an enhanced add_global_style() method that supports both inline style definitions and external CSS file imports.

Usage Examples

1. Inline Style Definition (Traditional)

app.add_global_style(
    selector=".my-button",
    styles={
        "background-color": "#4CAF50",
        "color": "white",
        "padding": "10px 20px",
        "border-radius": "5px"
    }
)

2. External CSS File Import (New in v1.1.2)

app.add_global_style(file_path="styles.css")

3. Combined Usage

# Add inline styles
app.add_global_style(
    selector=".primary-btn",
    styles={
        "background-color": "#007bff",
        "color": "white"
    }
)

# Import external CSS file
app.add_global_style(file_path="components.css")

Practical Example with External CSS

main.py:

from dars.all import *

app = App(title="Dars Styling Test")

index = Page(
    Container(
        Button("Styled Button", class_name="button-styling-test"),
        id="page_sub_container"
    )
)

app.add_page(name="index", root=index, title="index", index=True)
app.add_global_style(file_path="styles.css")

if __name__ == "__main__":
    app.rTimeCompile(add_file_types=".py, .css")

styles.css:

.button-styling-test {
    background-color: rgb(51, 255, 0);
    padding: 15px 30px;
    border-radius: 8px;
    border: none;
    font-weight: bold;
    cursor: pointer;
}

.button-styling-test:hover {
    background-color: rgb(30, 200, 0);
    transform: scale(1.05);
}

#page_sub_container {
    padding: 20px;
    background-color: #f5f5f5;
    min-height: 100vh;
}

Hot Reload for CSS Files

When using app.rTimeCompile() with the add_file_types=".css" parameter, the development server automatically watches for changes in CSS files and reloads the application when modifications are detected.

# Watch for both Python and CSS file changes
app.rTimeCompile(add_file_types=".py, .css")

# Or watch for CSS files only
app.rTimeCompile(add_file_types=".css")

Benefits of External CSS Import

  1. Separation of Concerns : Keep styles separate from application logic
  2. Better Organization : Maintain complex stylesheets in dedicated files
  3. Team Collaboration : Designers and developers can work simultaneously
  4. CSS Preprocessors : Use SASS, LESS, or other preprocessors
  5. Performance : Browser caching for external CSS files

Backward Compatibility

The enhanced method maintains full backward compatibility - existing code using add_global_style(selector, styles) will continue to work without modification.

PWA Configuration Properties

The App class includes these PWA-specific properties:

# Icons and visual resources
self.favicon = favicon  # Path to traditional favicon
self.icon = icon  # Main icon for PWA (multiple sizes)
self.apple_touch_icon = apple_touch_icon  # Icon for Apple devices
self.manifest = manifest  # Path to manifest.json file

# Colors and theme
self.theme_color = theme_color  # Theme color (#RRGGBB)
self.background_color = background_color  # Background color for splash screens

# Service Worker
self.service_worker_path = service_worker_path  # Path to service worker file
self.service_worker_enabled = service_worker_enabled  # Enable/disable

# Additional PWA configuration
self.pwa_enabled = config.get('pwa_enabled', False)
self.pwa_name = config.get('pwa_name', title)
self.pwa_short_name = config.get('pwa_short_name', title[:12])
self.pwa_display = config.get('pwa_display', 'standalone')
self.pwa_orientation = config.get('pwa_orientation', 'portrait')

Meta Tag Generation for PWA

The App class provides methods to generate PWA meta tags:

def get_meta_tags(self) -> Dict[str, str]:
    """Returns all meta tags as a dictionary"""
    meta_tags = {}

    # Viewport configured for responsiveness
    viewport_parts = []
    for key, value in self.config['viewport'].items():
        if key == 'initial_scale':
            viewport_parts.append(f'initial-scale={value}')
        elif key == 'user_scalable':
            viewport_parts.append(f'user-scalable={value}')
        else:
            viewport_parts.append(f'{key.replace("_", "-")}={value}')
    meta_tags['viewport'] = ', '.join(viewport_parts)

    # Specific tags for PWA
    meta_tags['theme-color'] = self.theme_color
    if self.pwa_enabled:
        meta_tags['mobile-web-app-capable'] = 'yes'
        meta_tags['apple-mobile-web-app-capable'] = 'yes'
        meta_tags['apple-mobile-web-app-status-bar-style'] = 'default'
        meta_tags['apple-mobile-web-app-title'] = self.pwa_short_name

    return meta_tags

Integration with HTML/CSS/JS Exporter

The HTMLCSSJSExporter uses the PWA configuration from the App class to generate:

  1. manifest.json file - Progressive web app configuration
  2. Meta tags - To indicate PWA capabilities in different browsers
  3. Icon references - For multiple devices and sizes
  4. Service Worker registration - For offline functionality

Example of Generated Manifest.json

{
  "name": "App Name",
  "short_name": "Short Name",
  "description": "Application description",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "orientation": "portrait",
  "icons": [
    {
      "src": "icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Service Worker Registration Script

The exporter automatically generates code to register the service worker:

if ('serviceWorker' in navigator && '{service_worker_path}') {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('{service_worker_path}')
      .then(function(registration) {
        console.log('ServiceWorker registration successful');
      })
      .catch(function(error) {
        console.log('ServiceWorker registration failed: ', error);
      });
  });
}

Complete PWA App Configuration Example

# Create a complete PWA application
app = App(
    title="My PWA App",
    description="An amazing progressive application",
    author="My Company",
    keywords=["pwa", "webapp", "productivity"],
    language="en",
    favicon="assets/favicon.ico",
    icon="assets/icon-192x192.png",
    apple_touch_icon="assets/apple-touch-icon.png",
    theme_color="#4A90E2",
    background_color="#FFFFFF",
    service_worker_path="sw.js",
    service_worker_enabled=True,
    pwa_enabled=True,
    pwa_name="My App",
    pwa_short_name="MyApp",
    pwa_display="standalone"
)

# Add pages and components
app.add_page("home", HomeComponent(), title="Home", index=True)
app.add_page("about", AboutComponent(), title="About")

Implementation Considerations

Browser Compatibility

Dars Framework's PWA implementation is compatible with: - Chrome/Chromium (full support) - Firefox (basic support) - Safari (limited support on iOS) - Edge (full support)

Dars - Components Documentation


Barrel Import

You can import all main components and modules with a single line:

from dars.all import *

This simplifies integration and improves developer experience by exposing components like Text , Button , Container , State , and DAP functions like log , alert , showModal , etc.

Note : Be careful if you create custom components with the same names as built-in components to avoid conflicts.


Introduction to Components

Components are the fundamental UI elements in Dars. Each component encapsulates its appearance, behavior, and state. In modern Dars, you should heavily rely on Utility Classes (Tailwind-like classes) using the class_name property instead of the old style dictionary, and use DAP Functions ( show() , hide() , log() , alert() , updateVRef() ) instead of writing raw inline JavaScript for events.

For custom components, refer to Custom Components .


Base Component Class

All UI elements inherit from the Component base class, which provides standard attributes and DOM manipulation methods.

Global Properties

  • id : Unique identifier for the component.
  • class_name : String containing CSS utility classes (e.g., "flex flex-col bg-slate-100 p-4 rounded-lg" ).
  • style : Optional dictionary or string for direct inline styles (prefer class_name ).
  • children : List of child components.
  • Events : Handlers like on_click , on_change , on_mouse_enter , etc. Accept DAP utility functions or state setters.

Core Components

Container

The Container acts as a generic <div> to group components.

from dars.all import *

layout = Container(
    Text("Welcome to Dars", style="text-4xl font-black text-slate-900 mb-4"),
    Button("Get Started", style="bg-indigo-600 text-white px-6 py-3 rounded-xl shadow-lg hover:bg-indigo-700 transition-all scale-105 active:scale-95"),
    style="flex flex-col items-center justify-center min-h-screen bg-slate-50"
)

Section

Similar to Container , but renders as a semantic <section> HTML tag.

main_section = Section(
    Text("About Us", style="text-xl font-semibold"),
    style="p-8 border-t border-slate-200"
)

Text

Displays static or reactive text.

title = Text(
    "Dars Framework",
    style="text-6xl font-black text-transparent bg-clip-text bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500"
)

# Reactive text using useDynamic
user_name = Text(useDynamic("user.name"), style="text-lg font-medium")

Interactive Components

Button

Creates interactive clickable buttons. Use DAP functions for events instead of inline JavaScript.

submit_btn = Button(
    "Save Changes",
    style="bg-emerald-500 text-white font-semibold py-3 px-6 rounded-lg transition-all hover:bg-emerald-600 hover:shadow-xl active:scale-95",
    on_click=log("Changes saved successfully!")
)

Input

Allows text or numeric user input.

email_input = Input(
    placeholder="Enter your email",
    input_type="email",
    required=True,
    style="w-full p-3 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500",
    on_change=log("Email updated")
)

Checkbox & RadioButton

For boolean selections or single-choice groups.

terms = Checkbox(
    label="I accept the terms",
    checked=False,
    style="accent-blue-500"
)

theme_radio = RadioButton(
    label="Dark Mode",
    name="theme_group",
    checked=True
)

Select

Dropdown menu for options.

dropdown = Select(
    options=["Option 1", "Option 2", "Option 3"],
    value="Option 1",
    style="p-2 border rounded bg-white text-slate-700"
)

Textarea

For multi-line inputs.

comment = Textarea(
    placeholder="Write your comment...",
    rows=4,
    style="w-full p-2 border border-slate-300 rounded resize-y"
)

Slider

Range selection slider.

volume = Slider(
    min_value=0, max_value=100, value=50,
    style="w-[200px] accent-blue-500"
)

FileUpload

upload = FileUpload(
    label="Upload Document",
    accept=".pdf",
    style="cursor-pointer bg-slate-100 p-2 rounded border-dashed border-2"
)

DatePicker

date = DatePicker(value="2025-01-01", style="p-2 rounded border")

Visual & Media Components

Image

logo = Image(
    src="/assets/logo.png",
    alt="Logo",
    style="w-[120px] h-auto object-contain drop-shadow-md"
)

Video & Audio

Wrappers over HTML5 media tags with reactivity support.

video_player = Video(
    src="/media/promo.mp4",
    controls=True,
    autoplay=False,
    style="w-full rounded-xl shadow-lg"
)

audio_player = Audio(
    src="/media/soundtrack.mp3",
    controls=True
)
nav_link = Link(
    "Go to Dashboard",
    href="/dashboard",
    style="text-blue-500 hover:text-blue-700 underline"
)

Advanced UI Components

Card

A structured container for displaying related info.

user_card = Card(
    title="Profile Info",
    children=[Text("Username: ZtaDev"), Button("Edit")],
    style="bg-white rounded-xl shadow-md p-6 max-w-sm border border-slate-100"
)

An overlay dialog. Modals are hidden by default to prevent flicker on load. Use showModal() and hideModal() DAP utilities to control them.

from dars.all import *

info_modal = Modal(
    id="info-modal",
    title="Information",
    is_open=False,
    children=[Text("Here are some details.")],
    style="bg-white rounded-lg p-6 w-[400px]",
    overlay_class="bg-black/50 backdrop-blur-sm"
)

# Open modal using the utility function
open_btn = Button("Show Info", on_click=showModal("info-modal"))

Top navigation bar structure.

nav = Navbar(
    brand=Text("MyApp", style="text-xl font-bold"),
    children=[Link("Home", "/"), Link("Settings", "/settings")],
    style="flex justify-between items-center bg-slate-900 text-white p-4 sticky top-0 z-50"
)

Accordion & Tabs

For collapsing content or switching between views.

faq = Accordion(
    items=[
        {"title": "What is Dars?", "content": "A reactive Python web framework."},
        {"title": "Is it fast?", "content": "Yes!"}
    ],
    style="border rounded"
)

dashboard_tabs = Tabs(
    tabs=[
        {"label": "Overview", "content": Text("Overview data")},
        {"label": "Metrics", "content": Text("Metrics data")}
    ]
)

Markdown

Renders Markdown as HTML. Built-in support for highlight.js code highlighting.

doc = Markdown(
    content="## Hello\nThis is **Markdown**",
    style="prose prose-slate max-w-none p-4"
)

ProgressBar & Tooltip

prog = ProgressBar(value=75, max_value=100, style="w-full h-2 bg-slate-200 rounded-full [&::-webkit-progress-value]:bg-blue-500")

tip = Tooltip(text="Click to save", child=Button("Save"))

Data Visualization

DataTable

Render tabular data from lists or Pandas DataFrames.

data = [
    {'Name': 'Alice', 'Role': 'Admin'},
    {'Name': 'Bob', 'Role': 'User'}
]
table = DataTable(data, striped=True, hover=True, theme="light", style="w-full text-left")

Chart

Renders Plotly.js charts.

import plotly.graph_objects as go
fig = go.Figure(data=[go.Bar(x=['A', 'B'], y=[10, 20])])

chart = Chart(figure=fig, style="w-full h-[400px]")

Layout Components

While you can use Container with CSS classes ( style="flex gap-4" ), Dars provides dedicated layout components for convenience.

FlexLayout

row = FlexLayout(
    direction="row",
    justify="space-between",
    align="center",
    gap="16px",
    children=[Button("Cancel"), Button("Confirm")],
    style="w-full p-4"
)

GridLayout

grid = GridLayout(
    rows=2, cols=2, gap="24px",
    children=[Text("1"), Text("2"), Text("3"), Text("4")],
    style="w-full max-w-4xl mx-auto"
)

Dynamic Creation & Lifecycle

Dars allows you to create and delete components dynamically at runtime, with full support for lifecycle hooks.

Runtime Component Manipulation

from dars.all import *

# Component to be generated
msg = Text("Dynamic Message", id="msg", style="text-emerald-600 font-medium")

# Creates the component inside the container with ID 'root'
create_btn = Button("Add Message", on_click=createComp(msg, root="root", position="append"))

# Deletes the component with ID 'msg'
delete_btn = Button("Remove Message", on_click=deleteComp("msg"))

Lifecycle Hooks (onMount, onUpdate, onUnmount)

Components can trigger DAP operations or callbacks when they are created, updated, or destroyed.

dynamic_box = Container(
    id="dyn_box",
    class_name="p-4 border rounded",
    onMount=log("dyn_box was added to DOM"),
    onUpdate=log("dyn_box was updated"),
    onUnmount=log("dyn_box was removed from DOM")
)
  • onMount : Runs once when the component is inserted into the DOM.
  • onUpdate : Runs after dynamic updates (e.g., via updateComp or reactive V() updates).
  • onUnmount : Runs right before the component is deleted from the DOM.

Control Flow Components

Dars provides three dedicated components for conditional rendering and list rendering. They work at both compile time (Python booleans and lists) and runtime (VRef expressions from setVRef / useFetch ).


Show — Runtime Visibility Toggle

Show always renders its children into the DOM. A falsy condition hides the wrapper with display:none . When the condition is a VRef expression, the browser runtime shows or hides the wrapper automatically whenever the VRef value changes.

Use Show for loading spinners, error banners, and any UI that toggles based on runtime state.

from dars.all import *

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

# Loading spinner — visible while is_loading is True
Show(
    is_loading,
    Container(
        Spinner(),
        Text("Loading tasks…", style="text-gray-500 ml-2"),
        style="flex items-center gap-2 mb-4",
    ),
)

# Error banner — hidden by default, shown when has_error becomes True
Show(
    has_error,
    Container(
        Text("Could not load tasks. Is the backend running?", style="text-red-600"),
        style="bg-red-50 border border-red-200 rounded p-3 mb-4",
    ),
)

Compile-time bool (static hide/show):

Show(condition=False, Text("Hidden at build time"))
# → renders with style="display:none"

Props:

Prop Type Description
condition bool or VRefValue Controls visibility
*children Component One or more child components to render
id str HTML id attribute
class_name str CSS class names
style dict Inline styles

Each — List Rendering

Each renders a list of items using a template function. It supports two modes:

  • Compile-time list — a plain Python list is unrolled at export time, one element per item.
  • Runtime VRef list — a VRefValue (from setVRef or useFetch ) emits a data-dap-each attribute; the browser re-renders the container whenever the VRef value changes.

Compile-Time List

from dars.all import *

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

Each(
    items=users,
    render=lambda u: Container(
        Text(u["name"], style="font-semibold"),
        Text(u["role"], style="text-sm text-gray-500"),
        style="flex justify-between p-2 border-b",
    ),
)

Runtime VRef List (from API)

When items is a VRefValue , the render function is called at export time with a sentinel dict containing __item_<field>__ placeholder strings. The resulting HTML template is stored in data-each-template . At runtime, dom_each_render substitutes real item values for each element in the list.

from dars.all import *

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

def task_item(t):
    # t is the sentinel dict at export time → placeholders like "__item_title__"
    # t is a real task dict at runtime → actual values
    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 mb-1 bg-white shadow-sm",
    )

Each(
    items=tasks_vref,
    render=task_item,
    class_name="space-y-1 mb-6 min-h-[40px]",
)

Special placeholders:

Placeholder Replaced with
__item_<field>__ The value of item["field"] for each item
__item_done_class__ "line-through text-gray-400" if item["done"] is truthy, "" otherwise

Supported API response shapes (automatically unwrapped by dom_each_render ):

Response shape Array used
{"tasks": [...]} tasks
{"items": [...]} items
{"data": [...]} data
{"results": [...]} results
[...] used directly

Props:

Prop Type Description
items list or VRefValue Items to render
render callable(item) -> Component Template function
item_key str Key field name for each item (default "id" )
id str HTML id attribute
class_name str CSS class names
style dict Inline styles

Styling System in Dars

Dars Framework introduces a powerful, Python-native utility class system inspired by Tailwind CSS. This system allows you to style your components using concise string utilities directly in your Python code, without needing Node.js, PostCSS, or any external build tools.

Tip: The official Dars Framework VS Code extension provides Tailwind-like utility style completions while editing style="..." strings in Python.

  • VS Code Marketplace: https://marketplace.visualstudio.com/items?itemName=ZtaMDev.dars-framework
  • Open VSX: https://open-vsx.org/extension/ztamdev/dars-framework

Overview

Instead of writing raw CSS dictionaries or separate CSS files, you can now use the style , hover_style , and active_style arguments with utility strings. These strings are parsed at runtime (and export time) into standard CSS dictionaries.

Example

from dars.all import *

def MyComponent():
    return Container(
        Text("Hello, Dars!"),
        style="bg-blue-500 p-4 rounded-lg shadow-md",
        hover_style="bg-blue-600 scale-105",
        active_style="scale-95"
    )

Supported Utilities

The system supports a comprehensive range of utilities covering layout, spacing, typography, colors, borders, effects, transforms, and more.

Arbitrary Properties (Tailwind-like)

You can set any CSS property using the prop-[value] syntax.

  • Property names use standard CSS (with - ). If you prefer, you can also write underscores ( _ ) and Dars will convert them to dashes.
  • Inside [value] , underscores ( _ ) are converted to spaces. This makes it easy to write complex CSS values inside a single class token.

Examples:

style="background-image-[linear-gradient(90deg,_rgba(0,0,0,.35),_#00ffcc)]"
style="background-color-[rgba(10,20,30,.6)]"
style="padding-[calc(1rem_+_2vw)]"
style="color-[var(--brand-color)]"
style="--brand-color-[#00ffcc]"

Composable properties

Some properties are composable, meaning multiple utilities will be appended (instead of overwriting the previous value):

  • filter
  • backdrop-filter
  • transform
style="filter-[blur(6px)] filter-[brightness(120%)]"

This produces:

filter: blur(6px) brightness(120%);

Layout & Display

  • Display : flex , inline-flex , grid , inline-grid , block , inline-block , inline , table , table-row , table-cell , hidden , contents , flow-root
  • Flex Direction : flex-row , flex-row-reverse , flex-col , flex-col-reverse
  • Flex Wrap : flex-wrap , flex-wrap-reverse , flex-nowrap
  • Justify Content : justify-start , justify-end , justify-center , justify-between , justify-around , justify-evenly
  • Justify Items : justify-items-start , justify-items-end , justify-items-center , justify-items-stretch
  • Align Items : items-start , items-end , items-center , items-baseline , items-stretch
  • Align Content : content-start , content-end , content-center , content-between , content-around , content-evenly
  • Align Self : self-auto , self-start , self-end , self-center , self-stretch , self-baseline
  • Flex Grow/Shrink : grow , grow-0 , shrink , shrink-0
  • Flex : flex-1 , flex-auto , flex-initial , flex-none
  • Order : order-{n}
  • Basis : basis-{value}

Grid

  • Grid Template Columns : grid-cols-{n} (e.g., grid-cols-3 , grid-cols-12 )
  • Grid Template Rows : grid-rows-{n}
  • Grid Column Span : col-span-{n} , col-start-{n} , col-end-{n}
  • Grid Row Span : row-span-{n} , row-start-{n} , row-end-{n}
  • Grid Auto Flow : grid-flow-row , grid-flow-col , grid-flow-dense , grid-flow-row-dense , grid-flow-col-dense
  • Grid Auto Columns : auto-cols-auto , auto-cols-min , auto-cols-max , auto-cols-fr
  • Grid Auto Rows : auto-rows-auto , auto-rows-min , auto-rows-max , auto-rows-fr
  • Gap : gap-{n} , gap-x-{n} , gap-y-{n}
  • Divide : divide-x-{n} , divide-y-{n} , divide-{color} (adds borders between children)

Spacing (Padding & Margin)

  • Padding : p-{n} (all sides), px-{n} (horizontal), py-{n} (vertical), pt-{n} (top), pr-{n} (right), pb-{n} (bottom), pl-{n} (left), ps-{n} (inline-start), pe-{n} (inline-end)
  • Margin : m-{n} , mx-{n} , my-{n} , mt-{n} , mr-{n} , mb-{n} , ml-{n} , ms-{n} , me-{n}
  • Margin Auto : mx-auto , my-auto
  • Values :
    • Numbers correspond to 0.25rem units (e.g., 4 = 1rem , 8 = 2rem )
    • Arbitrary values: p-[20px] , m-[5%]

Sizing

  • Width : w-{n} , w-full , w-screen , w-auto , w-min , w-max , w-fit , w-1/2 , w-1/3 , w-[300px]
  • Height : h-{n} , h-full , h-screen , h-auto , h-min , h-max , h-fit , h-[50vh]
  • Min Width : min-w-{n} , min-w-full , min-w-min , min-w-max , min-w-fit
  • Min Height : min-h-{n} , min-h-full , min-h-screen , min-h-min , min-h-max , min-h-fit
  • Max Width : max-w-{size} (xs, sm, md, lg, xl, 2xl-7xl, full, min, max, fit, prose, screen-{size})
  • Max Height : max-h-{n} , max-h-full , max-h-screen , max-h-min , max-h-max , max-h-fit , max-h-none
  • Size : size-{n} (sets both width and height)

Typography

  • Font Size : text-xs , text-sm , text-base , text-lg , text-xl , text-2xl , text-3xl , text-4xl , text-5xl , text-6xl , text-7xl , text-8xl , text-9xl
  • Font Size (Direct) : fs-[32px] , fs-[2rem] Direct font-size specification
  • Font Family : ffam-sans , ffam-serif , ffam-mono , ffam-[Open_Sans] , ffam-[Times+New+Roman]
  • Font Weight : font-thin , font-extralight , font-light , font-normal , font-medium , font-semibold , font-bold , font-extrabold , font-black
  • Font Style : italic , not-italic
  • Text Align : text-left , text-center , text-right , text-justify , text-start , text-end
  • Text Color : text-{color}-{shade} , text-[#123456]
  • Text Decoration : underline , overline , line-through , no-underline
  • Text Transform : uppercase , lowercase , capitalize , normal-case
  • Text Overflow : truncate , text-ellipsis , text-clip
  • Vertical Align : align-baseline , align-top , align-middle , align-bottom , align-text-top , align-text-bottom , align-sub , align-super
  • Whitespace : whitespace-normal , whitespace-nowrap , whitespace-pre , whitespace-pre-line , whitespace-pre-wrap , whitespace-break-spaces
  • Word Break : break-normal , break-words , break-all , break-keep
  • Line Height : leading-none , leading-tight , leading-snug , leading-normal , leading-relaxed , leading-loose , leading-{n}
  • Letter Spacing : tracking-tighter , tracking-tight , tracking-normal , tracking-wide , tracking-wider , tracking-widest
  • Text Indent : indent-{n}

Smart Property Switching: The text- prefix is smart. If you provide a size (e.g., text-xl ), it sets font-size . If you provide a color (e.g., text-white ), it automatically switches to set the color property.

Colors

Complete Palette (50-950 shades for each):

  • Neutrals : slate , gray , zinc , neutral , stone
  • Reds : red , rose
  • Oranges : orange , amber
  • Yellows : yellow , lime
  • Greens : green , emerald , teal
  • Blues : cyan , sky , blue , indigo
  • Purples : violet , purple , fuchsia
  • Pinks : pink
  • Special : black , white , transparent , current

Opacities: You can set opacity for background, text, border, and rings: - bg-opacity-{n} (0-100) - text-opacity-{n} (0-100) - border-opacity-{n} (0-100) - ring-opacity-{n} (0-100)

Usage Examples:

style="bg-cyan-500 text-white"        # Cyan background
style="bg-emerald-600 text-slate-50"  # Emerald background with light slate text
style="bg-fuchsia-500 text-rose-100"  # Fuchsia background with light rose text
style="bg-amber-400 text-zinc-900"    # Amber background with dark zinc text

Backgrounds

  • Background Color : bg-{color}-{shade} , bg-[#f0f0f0]
  • Background Images & Gradients :
    • bg-[linear-gradient(...)] (automatically maps to background-image )
    • bg-[radial-gradient(...)]
    • bg-[url(...)]
    • bgimg-[...] (direct background-image utility)
  • Background Position : bg-bottom , bg-center , bg-left , bg-left-bottom , bg-left-top , bg-right , bg-right-bottom , bg-right-top , bg-top
  • Background Repeat : bg-repeat , bg-no-repeat , bg-repeat-x , bg-repeat-y , bg-repeat-round , bg-repeat-space
  • Background Size : bg-auto , bg-cover , bg-contain
  • Background Attachment : bg-fixed , bg-local , bg-scroll
  • Background Clip : bg-clip-border , bg-clip-padding , bg-clip-content , bg-clip-text
  • Background Origin : bg-origin-border , bg-origin-padding , bg-origin-content
  • Gradients :
    • bg-gradient-to-{t|tr|r|br|b|bl|l|tl} : Sets the gradient direction.
    • from-{color} : Sets the start color.
    • via-{color} : Sets an intermediate color.
    • to-{color} : Sets the end color.

Gradient Example:

style="bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500"

Dars uses a modern CSS variable architecture for gradients ( --tw-gradient-stops ), ensuring they are performant and easy to manipulate. - Background Blend Mode : bg-blend-normal , bg-blend-multiply , bg-blend-screen , bg-blend-overlay , bg-blend-darken , bg-blend-lighten , bg-blend-color-dodge , bg-blend-color-burn , bg-blend-hard-light , bg-blend-soft-light , bg-blend-difference , bg-blend-exclusion , bg-blend-hue , bg-blend-saturation , bg-blend-color , bg-blend-luminosity

Borders & Radius

  • Border Width : border , border-0 , border-2 , border-4 , border-8
  • Border Width (Sides) : border-t-{n} , border-r-{n} , border-b-{n} , border-l-{n} , border-x-{n} , border-y-{n}
  • Border Color : border-{color}-{shade} , border-[#123456]
  • Border Color (Sides) : border-t-{color} , border-r-{color} , border-b-{color} , border-l-{color} , border-x-{color} , border-y-{color}
  • Border Style : border-solid , border-dashed , border-dotted , border-double , border-hidden , border-none
  • Border Radius : rounded , rounded-none , rounded-sm , rounded-md , rounded-lg , rounded-xl , rounded-2xl , rounded-3xl , rounded-full , rounded-[10px]
  • Border Radius (Corners) : rounded-t-{size} , rounded-r-{size} , rounded-b-{size} , rounded-l-{size} , rounded-tl-{size} , rounded-tr-{size} , rounded-br-{size} , rounded-bl-{size}

Effects

  • Box Shadow : shadow-sm , shadow , shadow-md , shadow-lg , shadow-xl , shadow-2xl , shadow-inner , shadow-none
  • Opacity : opacity-0 , opacity-25 , opacity-50 , opacity-75 , opacity-100
  • Rings :
    • ring , ring-{n} : Adds an outer ring (box-shadow) with a specific width (e.g., ring-4 ).
    • ring-{color} : Sets the color of the ring (e.g., ring-blue-500 ).
    • ring-offset-{n} : Adds a white offset between the element and the ring.
    • ring-opacity-{n} : Sets the transparency of the ring.

Ring Example:

style="ring-4 ring-indigo-500 ring-opacity-50 ring-offset-2"
  • Mix Blend Mode : mix-blend-normal , mix-blend-multiply , mix-blend-screen , mix-blend-overlay , mix-blend-darken , mix-blend-lighten , mix-blend-color-dodge , mix-blend-color-burn , mix-blend-hard-light , mix-blend-soft-light , mix-blend-difference , mix-blend-exclusion , mix-blend-hue , mix-blend-saturation , mix-blend-color , mix-blend-luminosity

Filters

  • Blur : blur-none , blur-sm , blur , blur-md , blur-lg , blur-xl , blur-2xl , blur-3xl
  • Brightness : brightness-{n} (0-200)
  • Contrast : contrast-{n} (0-200)
  • Grayscale : grayscale-{n} (0-100)
  • Hue Rotate : hue-rotate-{n} (degrees)
  • Invert : invert-{n} (0-100)
  • Saturate : saturate-{n} (0-200)
  • Sepia : sepia-{n} (0-100)
  • Drop Shadow : drop-shadow-sm , drop-shadow , drop-shadow-md , drop-shadow-lg , drop-shadow-xl , drop-shadow-2xl , drop-shadow-none

Backdrop Filters

  • Backdrop Blur : backdrop-blur-{size}
  • Backdrop Brightness : backdrop-brightness-{n}
  • Backdrop Contrast : backdrop-contrast-{n}
  • Backdrop Grayscale : backdrop-grayscale-{n}
  • Backdrop Hue Rotate : backdrop-hue-rotate-{n}
  • Backdrop Invert : backdrop-invert-{n}
  • Backdrop Opacity : backdrop-opacity-{n}
  • Backdrop Saturate : backdrop-saturate-{n}
  • Backdrop Sepia : backdrop-sepia-{n}

Transforms

  • Scale : scale-{n} , scale-x-{n} , scale-y-{n} (e.g., scale-110 = 110%)
  • Rotate : rotate-{n} (degrees)
  • Translate : translate-x-{n} , translate-y-{n}
  • Skew : skew-x-{n} , skew-y-{n} (degrees)
  • Transform Origin : origin-center , origin-top , origin-top-right , origin-right , origin-bottom-right , origin-bottom , origin-bottom-left , origin-left , origin-top-left

Transitions

  • Transition Property : transition-none , transition-all , transition-colors , transition-opacity , transition-shadow , transition-transform
  • Transition Duration : duration-{ms} (e.g., duration-300 , duration-500 )
  • Transition Delay : delay-{ms}
  • Transition Timing : ease-linear , ease-in , ease-out , ease-in-out

Positioning

  • Position : static , fixed , absolute , relative , sticky
  • Top/Right/Bottom/Left : top-{n} , right-{n} , bottom-{n} , left-{n}
  • Inset : inset-{n} , inset-x-{n} , inset-y-{n}
  • Z-Index : z-{n} (e.g., z-10 , z-50 )
  • Outline : outline-{n} , outline-{color} , outline-offset-{n} , outline-dashed , outline-dotted
  • Accent & Caret : accent-{color} , caret-{color}

Overflow

  • Overflow : overflow-auto , overflow-hidden , overflow-clip , overflow-visible , overflow-scroll
  • Overflow X : overflow-x-auto , overflow-x-hidden , overflow-x-clip , overflow-x-visible , overflow-x-scroll
  • Overflow Y : overflow-y-auto , overflow-y-hidden , overflow-y-clip , overflow-y-visible , overflow-y-scroll

Visibility

  • Visibility : visible , invisible , collapse

Cursor

  • Cursor Types : cursor-auto , cursor-default , cursor-pointer , cursor-wait , cursor-text , cursor-move , cursor-help , cursor-not-allowed , cursor-none , cursor-context-menu , cursor-progress , cursor-cell , cursor-crosshair , cursor-vertical-text , cursor-alias , cursor-copy , cursor-no-drop , cursor-grab , cursor-grabbing , cursor-all-scroll , cursor-col-resize , cursor-row-resize , cursor-n-resize , cursor-e-resize , cursor-s-resize , cursor-w-resize , cursor-ne-resize , cursor-nw-resize , cursor-se-resize , cursor-sw-resize , cursor-ew-resize , cursor-ns-resize , cursor-nesw-resize , cursor-nwse-resize , cursor-zoom-in , cursor-zoom-out

Pointer Events & User Select

  • Pointer Events : pointer-events-none , pointer-events-auto
  • User Select : select-none , select-text , select-all , select-auto

Object Fit & Position

  • Object Fit : object-contain , object-cover , object-fill , object-none , object-scale-down
  • Object Position : object-bottom , object-center , object-left , object-left-bottom , object-left-top , object-right , object-right-bottom , object-right-top , object-top

Aspect Ratio

  • Aspect Ratio : aspect-auto , aspect-square , aspect-video , aspect-{w}-{h} (e.g., aspect-16-9 )

Float & Clear

  • Float : float-right , float-left , float-none
  • Clear : clear-left , clear-right , clear-both , clear-none

Box Sizing

  • Box Sizing : box-border , box-content

Isolation

  • Isolation : isolate , isolation-auto

List Styles

  • List Style Type : list-none , list-disc , list-decimal
  • List Style Position : list-inside , list-outside

Appearance

  • Appearance : appearance-none , appearance-auto

Resize

  • Resize : resize-none , resize-y , resize-x , resize

Scroll Behavior

  • Scroll Behavior : scroll-auto , scroll-smooth
  • Scroll Snap Align : snap-start , snap-end , snap-center , snap-align-none
  • Scroll Snap Stop : snap-normal , snap-always
  • Scroll Snap Type : snap-none , snap-x , snap-y , snap-both , snap-mandatory , snap-proximity

Scroll Margin & Padding

  • Scroll Margin : scroll-m-{n} , scroll-mx-{n} , scroll-my-{n} , scroll-mt-{n} , scroll-mr-{n} , scroll-mb-{n} , scroll-ml-{n}
  • Scroll Padding : scroll-p-{n} , scroll-px-{n} , scroll-py-{n} , scroll-pt-{n} , scroll-pr-{n} , scroll-pb-{n} , scroll-pl-{n}

Touch Action

  • Touch Action : touch-auto , touch-none , touch-pan-x , touch-pan-left , touch-pan-right , touch-pan-y , touch-pan-up , touch-pan-down , touch-pinch-zoom , touch-manipulation

Will Change

  • Will Change : will-change-auto , will-change-scroll , will-change-contents , will-change-transform

Columns

  • Columns : columns-{n}

Break After/Before/Inside

  • Break After : break-after-{value}
  • Break Before : break-before-{value}
  • Break Inside : break-inside-{value}

Arbitrary Values

For values not covered by the standard scale, use square brackets [] :

Container(
    style="w-[350px] bg-[#1a2b3c] z-[100] top-[50px] fs-[24px]"
)

Arbitrary value features: - Use underscores for spaces: bg-[url('image.jpg')] bg-[url('image.jpg')] - Works with any property: p-[20px] , m-[5%] , w-[calc(100%-50px)]

State Variants

You can define styles for specific states using hover_style and active_style arguments.

Button(
    "Click Me",
    style="bg-blue-500 text-white px-4 py-2 rounded transition-all duration-300",
    hover_style="bg-blue-600 shadow-lg scale-105",
    active_style="bg-blue-700 scale-95"
)

Complete Example Styling

from dars.all import *

app = App("Styling Demo")

@route("/")
def index():
    return Page(
        Container(
            # Header with gradient text
            Text(
                "Welcome to Dars",
                style="fs-[48px] font-black text-center mb-8"
            ),

            # Card with new colors
            Container(
                Text("Cyan Card", style="text-xl font-bold mb-2"),
                Text("Using the new cyan color palette", style="text-cyan-100"),
                style="bg-cyan-600 p-6 rounded-xl shadow-xl mb-4"
            ),

            Container(
                Text("Emerald Card", style="text-xl font-bold mb-2"),
                Text("With emerald green background", style="text-emerald-100"),
                style="bg-emerald-600 p-6 rounded-xl shadow-xl mb-4"
            ),

            # Interactive button
            Button(
                "Hover Me",
                style="bg-fuchsia-500 text-white px-6 py-3 rounded-lg transition-all duration-300",
                hover_style="bg-fuchsia-600 scale-110 shadow-2xl",
                active_style="scale-95"
            ),

            style="max-w-4xl mx-auto p-8"
        ),
        style="min-h-screen bg-gradient-to-br from-slate-50 to-zinc-100"
    )

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

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

Performance

The parsing happens in Python before the HTML/CSS is generated. This means:

  1. Zero Runtime Overhead : The browser receives standard CSS
  2. No JavaScript Dependency : No need to load a large utility CSS library or run JS parsers in the browser
  3. Optimized Output : Only the styles you use are generated (as inline styles or extracted CSS)
  4. Python-Native : No Node.js, PostCSS, or build tools required

Best Practices

  1. Use semantic spacing : Stick to the 0.25rem scale (4, 8, 12, 16, etc.) for consistency
  2. Leverage the color palette : Use the predefined color shades for a cohesive design
  3. Combine with transitions : Add transition-all duration-300 for smooth hover effects
  4. Use arbitrary values sparingly : Prefer standard utilities when possible
  5. Keep it readable : Break long utility strings into multiple lines if needed
style="""
    bg-gradient-to-r from-purple-600 to-pink-600
    text-white px-8 py-4 rounded-xl shadow-2xl
    transition-all duration-300
"""

For animations, see the Animation System documentation.

Custom Utilities

You can define your own utility classes in dars.config.json under the utility_styles key. This allows you to create reusable style combinations, use raw CSS properties, and even compose other custom utilities.

Configuration ( dars.config.json ):

{
  "utility_styles": {
    "btn-primary": [
      "bg-blue-600", 
      "text-white", 
      "p-3", 
      "rounded-lg", 
      "hover:bg-blue-700", 
      "transition-all"
    ],
    "card-fancy": [
      "bg-white", 
      "p-8", 
      "rounded-xl", 
      "shadow-lg", 
      "border: 1px solid #e5e7eb"  // Raw CSS property
    ],
    "text-gradient": [
      "font-bold",
      "text-4xl",
      "background: linear-gradient(to right, #4f46e5, #ec4899)",
      "-webkit-background-clip: text",
      "-webkit-text-fill-color: transparent",
      "display: inline-block"
    ]
  }
}

Usage in Python:

Button(text="Click Me", style="btn-primary")
Container(style="card-fancy text-gradient")

Features: - Composition : Combine multiple existing utilities into one class. - Raw CSS : Use standard CSS syntax (e.g., border: 1px solid red ) directly in the list.

Custom Components in Dars Framework

Dars offers two ways to create custom components: Function Components (Recommended) and Class Components (Legacy).

Function Components are the modern way to create reusable UI elements. They use simple functions with f-string templates and automatically handle framework features like IDs, styling, and events.

Basic Syntax

Use the @FunctionComponent decorator. You can access framework properties ( id , class_name , style , children ) using the Props helper object or by declaring them as arguments.

Option 1: Using Props Object (Cleanest)

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>
    """

# Usage
card = UserCard("John Doe", "john@example.com", id="user-1", style="p-5")

Option 2: Explicit Arguments

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

Key Features

  1. Automatic Property Injection : The framework automatically injects the correct HTML attributes for {id} , {class_name} , and {style} .
  2. State V2 Compatible : Function components work seamlessly with State() and reactive updates.
  3. Event Handling : Events like on_click are handled automatically by the framework (passed via **props ).
  4. Children Support : Use {Props.children} or {children} to render nested content.

Example with State and Events

@FunctionComponent
def Counter(**props):
    return f"""
    <div {Props.id} {Props.class_name} {Props.style}>
        0 {Props.children}
    </div>
    """

# Create component with initial value "0"
counter = Counter(id="my-counter", children="0")

# Make it reactive controlling the 'text' property (textContent)
# Note: This replaces the entire content of the div with the new text
state = State(counter, text="0")

# Update it
Button("Increment", on_click=state.text.set("5"))

Using Hooks in FunctionComponents

FunctionComponents work seamlessly with all Dars hooks, enabling reactive and interactive behavior.

useDynamic() - Reactive Bindings

Use useDynamic() to create reactive text that updates automatically when state changes:

from dars.all import *

userState = State("user", name="John Doe", status="Active")

@FunctionComponent
def UserCard(**props):
    return f'''
    <div {Props.id} {Props.class_name} {Props.style}>
        <h3>Name: {useDynamic("user.name")}</h3>
        <p>Status: {useDynamic("user.status")}</p>
        {Props.children}
    </div>
    '''

# The name and status will update automatically when state changes
card = UserCard(id="user-card")
Button("Update", on_click=userState.name.set("Jane Doe"))

useValue() - Initial Values with Selectors

Use useValue() with selectors to set initial values and enable value extraction:

from dars.all import *

app = App("Example of hooks")

userState = State("user", name="Jane Doe", email="jane@example.com", display="None")

@FunctionComponent
def UserForm(**props):
    return f'''
    <div {Props.id} {Props.class_name} {Props.style}>
        <input value="{useValue("user.name", ".name-input")}" />
        <input value="{useValue("user.email", "#email-field")}" />
        <span>{useValue("user.age", ".age-display")}</span>
        <span>{useDynamic("user.display")}</span>
    </div>
    '''


@route("/")
def index():
    return Page(
        UserForm(id="user-form"),
        # Extract values using V() helper with the selectors
        Button(
            "Get Name",
            on_click=userState.display.set(
                "Name: " + V(".name-input")  # Extract current value
            )
        ),

        Button(
            "Combine Values",
            on_click=userState.display.set(
                V(".name-input") + " (" + V("#email-field") + ")"
            )
        )
    )

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

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

useWatch() - Side Effects

Use useWatch() to monitor state changes and execute side effects:

from dars.all import *

app = App("Example of hooks")

cartState = State("cart", total=0.0)

@FunctionComponent
def CartSummary(total=0,**props):
    return f'''
    <div {Props.id} {Props.class_name} {Props.style}>
        <h3>Cart Total: ${useDynamic("cart.total")}</h3>
        {Props.children}
    </div>
    '''

# Watch for cart changes and log
app.useWatch("cart.total", log("Cart total changed!"))

@route("/")
def index():
    return Page(
        CartSummary(id="cart-summary", total=0),
        # Button to add $10 to cart total using V() with state path
        Button("Add $10", on_click=cartState.total.set(
            V("cart.total").float() + 10
        ))
    )

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

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

Combining Multiple Hooks

You can combine multiple hooks for complex interactive components:

from dars.all import *

app = App("Example of hooks")

productState = State("product", 
    name="Widget", 
    price=19.99, 
    quantity=1,
    total=19.99
)

@FunctionComponent
def ProductCard(**props):
    return f'''
    <div {Props.id} {Props.class_name} {Props.style}>
        <!-- useDynamic for reactive display -->
        <h3>{useDynamic("product.name")}</h3>
        <p>Price: ${useDynamic("product.price")}</p>
        <!-- useValue for editable quantity -->
        <h3>Number to multiply with price</h3>
        <input type="number" 
               value="{useValue("product.quantity", ".qty-input")}"
               min="1" />


        <!-- useDynamic for calculated total -->
        <p>Total: ${useDynamic("product.total")}</p>

        {Props.children}
    </div>
    '''

# Watch for total changes and show alert
app.useWatch("product.total", log("Total updated!"))

@route("/")
def index():
    return Page(
        ProductCard(id="product-card",name="Milk", price=100, quantity=0, total=0 ),
        Button(
            "Calculate Total",
            on_click=productState.total.set(
                V(".qty-input").int() * V("product.price").float()
            )
        )

    )

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

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

Class Components (Legacy)

This is the older method of creating components by inheriting from the Component class. It is more verbose and requires manual handling of rendering logic.

from dars.all import *
from dars.core.component import Component

class CustomComponent(Component):
    def __init__(self, title: str, id: str = None, **props):
        super().__init__(**props)
        self.title = title
        self.id = id
        # Manual event attachment
        self.set_event(EventTypes.CLICK, log('click'))

    def render(self, exporter: 'Exporter') -> str:
        # Manual children rendering
        children_html = self.render_children(exporter)

        return f'''
        <div class="my-component" id="{self.id}" style="{self.style}">
            <h2>{self.title}</h2>
            <div class="content">
                {children_html}
            </div>
        </div>
        '''

Dars Animation System

Dars provides a powerful and easy-to-use animation system built on top of the Web Animations API. It allows you to add professional-grade animations to your components with simple Python function calls.

Animation Overview

All animations in Dars are dScript objects. This means they: - Run entirely on the client side (zero latency) - Can be assigned to any event handler ( on_click , on_mouseover , etc.) - Can be chained together using .then() or the sequence() helper - Return Promises, allowing for complex orchestration

Animation Quick Start

from dars.all import *

# Simple fade in
button.on_click = fadeIn(id="my-element")

# Chain animations
button.on_click = sequence(
    fadeOut(id="old-panel"),
    fadeIn(id="new-panel")
)

Animation Reference

Fade Animations

Control visibility with opacity transitions.

fadeIn(id, duration=300, easing="ease")

Fades an element in from opacity 0 to 1. Sets display: block automatically.

fadeIn(id="modal", duration=500)

fadeOut(id, duration=300, easing="ease", hide=True)

Fades an element out from current opacity to 0. - hide : If True (default), sets display: none after animation completes.

fadeOut(id="notification", duration=2000, hide=True)

Slide Animations

Move elements into or out of view.

slideIn(id, direction="down", duration=300, easing="ease")

Slides an element into its final position. - direction : "up" , "down" , "left" , "right" (from where it enters)

slideIn(id="sidebar", direction="left", duration=400)

slideOut(id, direction="up", duration=300, easing="ease", hide=True)

Slides an element out of view. - direction : "up" , "down" , "left" , "right" (to where it exits)

slideOut(id="sidebar", direction="left", duration=400)

Scale Animations

Zoom elements in and out.

scaleIn(id, duration=300, easing="ease", from_scale=0.0)

Scales an element up to its natural size (scale 1). - from_scale : Starting scale factor (0.0 to 1.0)

scaleIn(id="popup", from_scale=0.5)

scaleOut(id, duration=300, easing="ease", to_scale=0.0, hide=True)

Scales an element down. - to_scale : Ending scale factor (0.0 to 1.0)

scaleOut(id="popup", to_scale=0.0)

Attention Seekers

Draw user attention to elements.

shake(id, intensity=5, duration=500)

Shakes an element horizontally. Great for error feedback. - intensity : Shake distance in pixels.

shake(id="login-form", intensity=10)

bounce(id, distance=20, duration=600)

Bounces an element vertically. - distance : Bounce height in pixels.

bounce(id="notification-icon", distance=15)

pulse(id, scale=1.1, duration=400, iterations=1)

Pulses an element (scales up and down). - scale : Max scale during pulse. - iterations : Number of pulses. Use "infinite" for continuous pulsing.

# Single pulse
pulse(id="heart-icon")

# Continuous heartbeat
pulse(id="status-dot", iterations="infinite", duration=1000)

Transformations

Rotate and flip elements.

rotate(id, degrees=360, duration=500, easing="ease")

Rotates an element.

rotate(id="refresh-icon", degrees=180)

flip(id, axis="y", duration=600)

Flips an element 180 degrees around an axis. - axis : "x" (horizontal flip) or "y" (vertical flip).

flip(id="card", axis="y")

Property Transitions

Animate specific CSS properties.

colorChange(id, from_color, to_color, duration=500, property="background-color")

Smoothly transitions a color property.

colorChange(id="btn", from_color="#fff", to_color="#f00", property="background-color")

morphSize(id, to_width, to_height, duration=500, easing="ease")

Changes the dimensions of an element.

morphSize(id="panel", to_width="100%", to_height="500px")

Scroll & Viewport Animations

Dars provides a powerful, highly optimized Web Animations API-based engine for scroll and viewport-triggered animations. These run autonomously on the client, without eval() or string-execution vulnerabilities.

animateOnView(id, keyframes, duration=300, easing="ease", threshold=0.1, ...)

Triggers an animation as soon as an element enters the viewport. - keyframes : A list of dictionaries representing CSS properties (e.g., [{"opacity": "0"}, {"opacity": "1"}] ). - threshold : How much of the element must be visible (0.0 to 1.0) before triggering. - once : If True , the animation only runs the first time it enters the viewport.

# Fade and slide up when scrolled into view
index.add_script(animateOnView("my-card", [
    {"opacity": "0", "transform": "translateY(50px)"},
    {"opacity": "1", "transform": "translateY(0)"}
], duration=800, threshold=0.2))

staggerOnView(ids, keyframes, duration=300, stagger_delay=100, threshold=0.1, ...)

Animates a list of elements sequentially with a delay as soon as the first element enters the viewport. - ids : List of element IDs. - stagger_delay : Milliseconds to wait between starting each element's animation.

index.add_script(staggerOnView(
    ["card-1", "card-2", "card-3"],
    [{"opacity": "0", "transform": "scale(0.9)"},
     {"opacity": "1", "transform": "scale(1)"}],
    duration=500, stagger_delay=150
))

scrollProgress(id, property, from_val, to_val, unit="", start=0, end=1)

Ties a CSS property directly to the page's scroll progress (0.0 = top of page, 1.0 = bottom). - property : CSS property to animate (e.g., "opacity" , "translateY" ). - start / end : The scroll progress range where the animation occurs.

# Fades out the hero section as the user scrolls down
index.add_script(scrollProgress("hero-section", "opacity", 1.0, 0.0, start=0, end=0.2))

runOnView(id, code, threshold=0.1, once=True)

Executes arbitrary JavaScript code (via a secure callback mechanism) when an element enters the viewport.

index.add_script(runOnView("trigger-zone", "console.log('User reached the bottom!');"))

Chaining & Sequencing

You can run animations in sequence using the sequence() helper or the .then() method.

Using sequence()

The easiest way to run animations one after another.

from dars.all import sequence, fadeIn, slideIn

button.on_click = sequence(
    fadeIn(id="header"),
    slideIn(id="content", direction="up"),
    fadeIn(id="footer")
)

Using .then()

For more granular control or branching logic.

anim1 = fadeIn(id="box1")
anim2 = slideIn(id="box2")

# Run anim1, then anim2
button.on_click = anim1.then(anim2)

Parallel Animations

To run animations simultaneously, simply trigger them in the same event handler (or use a list of handlers).

# Both start at the same time
button.on_click = [
    fadeIn(id="box1"),
    slideIn(id="box2")
]

SPA Routing in Dars Framework

Dars Framework 1.4.5 introduces a powerful SPA (Single Page Application) routing system that supports nested routes, layouts, and automatic 404 handling.

Basic Routing

To create a basic SPA, you define pages and add them to your app. One page must be designated as the index.

from dars.all import *

app = App(title="My SPA App")

# Create pages
home = Page(Container(Text("Home Page")))
about = Page(Container(Text("About Us")))

# Add pages to app
app.add_page(name="home", root=home, route="/", title="Home", index=True)
app.add_page(name="about", root=about, route="/about", title="About")

you can also use the @route decorator to add pages to your app.

from dars.all import *

app = App(title="My SPA App")

# Create pages
home = Page(Container(Text("Home Page")))
about = Page(Container(Text("About Us")))

# Add pages to app
@app.route("/")
def home():
    return Page(Container(Text("Home Page")))

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

app.add_page("home", home, title="Home", index=True)
app.add_page("about", about, title="About")

Note: If you use the @route decorator, you can't add the route of the page in app.add_page() .

Nested Routes & Layouts

Nested routes allow you to create layouts that persist while child content changes. This is achieved using the parent parameter and the Outlet component.

The Outlet Component

The Outlet component serves as a placeholder where child routes will be rendered within a parent layout.

from dars.components.advanced.outlet import Outlet

# Parent Layout (Dashboard)
dashboard_layout = Page(
    Container(
        Text("Dashboard Header"),
        # Child routes will render here:
        Outlet(),
        Text("Dashboard Footer")
    )
)

# Child Page (Settings)
settings_page = Page(
    Container(Text("Settings Content"))
)

The Outlet can also render an optional placeholder while the child route is still loading (SSR lazy-load or SPA navigation). If placeholder is not provided, nothing is rendered.

from dars.components.advanced.outlet import Outlet

dashboard_layout = Page(
    Container(
        Text("Dashboard Header"),
        Outlet(
            placeholder=Container(Text("Loading section..."))
        ),
        Text("Dashboard Footer")
    )
)

Multiple Outlets (outlet_id)

You can declare multiple outlets in the same layout by giving each Outlet an outlet_id . Child routes can then target a specific outlet via app.add_page(..., outlet_id="...") .

from dars.components.advanced.outlet import Outlet

dashboard_layout = Page(
    Container(
        Text("Dashboard Header"),
        Container(
            Outlet(outlet_id="main"),
            Outlet(outlet_id="sidebar", placeholder=Text("Loading sidebar...")),
            style="flex gap-4"
        ),
        Text("Dashboard Footer")
    )
)

Configuring Nested Routes

Use the parent parameter in add_page to define the hierarchy.

# 1. Add the parent route
app.add_page(
    name="dashboard", 
    root=dashboard_layout, 
    route="/dashboard", 
    title="Dashboard"
)

# 2. Add the child route, specifying the parent's name
app.add_page(
    name="settings", 
    root=settings_page, 
    route="/dashboard/settings", 
    title="Settings",
    parent="dashboard"  # This links it to the dashboard layout
)

If your parent layout contains multiple outlets, pass outlet_id in the child route to target the correct outlet:

app.add_page(
    name="dashboard",
    root=dashboard_layout,
    route="/dashboard",
    title="Dashboard"
)

app.add_page(
    name="settings",
    root=settings_page,
    route="/dashboard/settings",
    title="Settings",
    parent="dashboard",
    outlet_id="main"
)

When you navigate to /dashboard/settings , Dars will render the dashboard layout and place the settings content inside the Outlet .

Trailing Slashes

The SPA router normalizes paths so that trailing slashes do not create false 404s:

  • /dashboard and /dashboard/ are treated as the same route.
  • The root path / remains / .

404 Handling

Dars provides robust handling for non-existent routes.

Default 404 Page

If a user navigates to a route that doesn't exist, Dars automatically: 1. Redirects the user to /404 . 2. Displays a built-in, clean "404 Page Not Found" error page.

Custom 404 Page

You can customize the 404 page using app.set_404_page() .

# Create your custom 404 page
not_found_page = Page(
    Container(
        Text("Oops! Page not found 😢", style="text-3xl"),
        Link("Go Home", href="/")
    )
)

# Register it
app.set_404_page(not_found_page)

Now, when a 404 occurs, users will be redirected to /404 but will see your custom design.

403 Handling

Similar to 404 pages, you can define a custom 403 Forbidden page for unauthorized access to private routes.

Default 403 Page

Dars includes a default 403 page that informs users they don't have permission to access the requested resource.

Custom 403 Page

You can customize the 403 page using app.set_403_page() .

# Create your custom 403 page
forbidden_page = Page(
    Container(
        Text("⛔ Access Denied", style="text-3xl text-red-500"),
        Text("You do not have permission to view this page."),
        Link("Go to Login", href="/login")
    )
)

# Register it
app.set_403_page(forbidden_page)

Dars will automatically redirect to /prohibited and show this page when a user tries to access a private route without authentication.

Hot Reload

The development server ( dars dev ) includes an intelligent hot reload system for SPAs:

  • Automatic Detection : The browser automatically detects changes to your Python code.
  • Smart Polling : It checks for updates every 500ms without spamming your console logs.
  • Retry Limit : If the server goes down, the client stops polling after 10 consecutive errors to prevent browser lag.
  • State Preservation : When possible, navigation state is preserved across reloads.

SEO & Metadata

Dars handles SEO automatically in Single Page Applications. The router intelligently updates the document metadata when navigating between routes.

Using the Head Component

To control page metadata for each route, use the Head component:

from dars.components.advanced.head import Head

@app.route("/about")
def about():
    return Page(
        Head(
            title="About Us - My App",
            description="Learn more about our company.",
            og_image="/images/about-og.jpg"
        ),
        Container(Text("About Content"))
    )

The router dynamically updates: - <title> - Meta tags ( description , keywords , etc.) - Open Graph tags ( og:title , og:type , etc.) - Twitter Cards

This ensures that even client-side routes display the correct information in the browser tab and when shared on social media.


Route Types & Security Guards

Dars provides four route types that control rendering strategy and access control:

RouteType Enum

Type Value Description
RouteType.PUBLIC "public" Client-side rendered, no auth required. Default.
RouteType.SSR "ssr" Server-side rendered, fetched from backend on navigation.
RouteType.PRIVATE "private" Requires authentication. Not included in initial bundle. Redirects to login.
RouteType.PROTECTED "protected" Requires authentication AND specific roles/permissions.

Using Route Types

from dars.core.route_types import RouteType

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

@route("/dashboard", route_type=RouteType.SSR)
def dashboard():
    return Page(Text("Dashboard"))

@route("/account", route_type=RouteType.PRIVATE)
def account():
    return Page(Text("My Account"))

@route("/admin", route_type=RouteType.PROTECTED, roles=["admin"])
def admin_panel():
    return Page(Text("Admin Panel"))

Route Guards

The @guard decorator provides fine-grained access control:

from dars.core.routing import guard

@route("/premium")
@guard(requires_auth=True, roles=["premium"], redirect="/pricing")
def premium_page():
    return Page(...)

Or inline via the RouteGuard class:

from dars.core.route_types import RouteGuard

@route("/enterprise", guard=RouteGuard(
    requires_auth=True,
    roles=["enterprise"],
    redirect="/contact-sales",
))
def enterprise():
    return Page(...)

Custom Guard Checks

@route("/beta")
@guard(
    requires_auth=True,
    custom_check=lambda user: user.get("beta_access", False),
)
def beta_page():
    return Page(...)

How Guards Work

  1. Client-side : The SPA router checks the RouteGuard configuration before loading a route
  2. Unauthenticated : Redirects to /login (or custom redirect path)
  3. No matching role : Shows 403 forbidden page
  4. Custom check fails : Blocked without rendering

Server-Side Rendering (SSR)

Dars Framework provides complete Server-Side Rendering support integrated with FastAPI, allowing you to build full-stack applications with both server-rendered and client-side pages.

Quick Overview

SSR routes are rendered on the server before being sent to the client, providing: - Faster initial page load - Progressive enhancement - Flexible architecture (mix SSR, SPA, and Static routes)

Basic SSR Route

from dars.all import *
from backend.apiConfig import DarsEnv

# Configure SSR URL
ssr_url = DarsEnv.get_urls()['backend']
app = App(title="My App", ssr_url=ssr_url)

# Define SSR route
@route("/", route_type=RouteType.SSR)
def home():
    return Page(
        Text("Welcome!", style="text-3xl font-bold"),
        Text("This page is rendered on the server!")
    )

app.add_page("home", home(), title="Home")

Dual Hydration System

Dars uses a sophisticated "Dual Hydration" approach:

  1. Server Side : Renders component to HTML and builds VDOM snapshot
  2. Client Side : Displays server HTML immediately, then hydrates with JavaScript
  3. Result : No flickering, instant content, full interactivity

This prevents Flash of Unstyled Content (FOUC), double rendering, and race conditions.

Creating an SSR Project

Use the Dars CLI to scaffold a complete SSR project with FastAPI backend:

dars init my-ssr-app --type ssr
cd my-ssr-app

This creates a full-stack project with: - Frontend Dars app ( main.py ) - FastAPI backend ( backend/api.py ) - Environment configuration - Development and production setup

Complete SSR Documentation

For comprehensive SSR documentation including: - Architecture and how it works - Development workflow - API reference ( create_ssr_app , SSRRenderer ) - Mixing SSR, SPA, and Static routes - Deployment guide - Advanced features (authentication, custom endpoints) - Best practices and troubleshooting - Real-world examples

See the Complete SSR Guide


Server-Side Rendering in Dars Framework

Dars Framework provides a complete Server-Side Rendering solution integrated with FastAPI, allowing you to build full-stack applications with both server-rendered and client-side pages in a single codebase.

Overview

SSR in Dars renders your pages on the server before sending them to the client, providing:

  • Faster Initial Load : Users see content immediately without waiting for JavaScript
  • Flexible Architecture : Mix SSR and SPA routes in the same application

Architecture

How SSR Works in Dars

  1. Client Request : Browser requests a page (e.g., /dashboard )
  2. Server Rendering : FastAPI backend renders the Dars component to HTML
  3. HTML Response : Server sends fully-rendered HTML with embedded VDOM
  4. Client Hydration : Browser loads JavaScript and "hydrates" the static HTML
  5. Interactive : Page becomes fully interactive with event handlers

Dual Hydration System

Dars uses a sophisticated "Dual Hydration" approach to prevent flickering and ensure smooth transitions:

Server Side:
1. Render component to HTML
2. Build VDOM representation
3. Inject VDOM as window.__ROUTE_VDOM__
4. Send HTML + VDOM to client

Client Side:
1. Display server-rendered HTML (instant)
2. Load dars.min.js runtime
3. Detect __ROUTE_VDOM__ presence
4. Hydrate DOM without re-rendering
5. Attach event handlers and state

This prevents: - Flash of Unstyled Content (FOUC) - Double rendering - Race conditions - Lost event handlers

SEO & Metadata

SSR routes are fully SEO-optimized. Using the Head component allows you to inject metadata directly into the server-rendered HTML.

@route("/blog/post-1", route_type=RouteType.SSR)
def blog_post():
    return Page(
        Head(
            title="My Amazing Blog Post",
            description="Read this incredible story...",
            keywords="blog, story, amazing",
            og_type="article"
        ),
        # ... content ...
    )

Quick Start

Creating an SSR Project

Use the Dars CLI to scaffold a complete SSR project:

dars init my-ssr-app --type ssr
cd my-ssr-app

This creates:

my-ssr-app/
├── main.py              # Frontend (Dars app)
├── backend/
│   ├── api.py          # FastAPI server with SSR
│   └── apiConfig.py    # Environment configuration
└── dars.config.json    # Dars configuration

Project Structure

Frontend ( main.py )

from dars.all import *
from backend.apiConfig import DarsEnv

# Configure SSR URL
ssr_url = DarsEnv.get_urls()['backend']
app = App(title="My SSR App", ssr_url=ssr_url)

# Define SSR route
@route("/", route_type=RouteType.SSR)
def index():
    return Page(
        Text("Hello from Server!", style="fs-[32px]"),
        Button("Click Me", on_click=alert("Interactive!"))
    )

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

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

Backend ( backend/api.py )

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from dars.backend.ssr import create_ssr_app
from apiConfig import DarsEnv

# Import Dars app
import sys
sys.path.insert(0, '.')
from main import app as dars_app

# Create FastAPI app with SSR
app = create_ssr_app(dars_app)

# Enable CORS for development
if DarsEnv.is_dev():
    urls = DarsEnv.get_urls()
    app.add_middleware(
        CORSMiddleware,
        allow_origins=[urls['frontend']],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=3000)

Environment Config ( backend/apiConfig.py )

class DarsEnv:
    MODE = "development"  # or "production"

    DEV = "development"
    BUILD = "production"

    @staticmethod
    def is_dev():
        return DarsEnv.MODE == DarsEnv.DEV

    @staticmethod
    def get_urls():
        if DarsEnv.is_dev():
            return {
                "backend": "http://localhost:3000",
                "frontend": "http://localhost:8000"
            }
        return {
            "backend": "/",
            "frontend": "/"
        }

Route Types

Dars supports two routing modes that can be mixed in the same application:

1. SSR Routes ( RouteType.SSR )

Server-rendered on every request.

@route("/dashboard", route_type=RouteType.SSR)
def dashboard():
    return Page(
        Text("Dashboard", style="text-3xl font-bold"),
        Text(f"Rendered at: {datetime.now()}")
    )

When to use: - SEO-critical pages (landing pages, blog posts) - Dynamic content that changes frequently - Pages requiring authentication checks server-side - Initial load performance is critical

2. SPA Routes ( RouteType.PUBLIC ) - Default

Client-side navigation, no server rendering.

@route("/settings")  # Default is PUBLIC
def settings():
    return Page(
        Text("Settings", level=1),
        # Interactive forms, real-time updates
    )

When to use: - Admin dashboards (not recommended for now in the future will be supported with Dars Middleware) - Interactive tools - Pages behind authentication - Real-time applications

SSR Lazy-Load Placeholders (SPA Navigation)

When you navigate to an RouteType.SSR route from the SPA router, the client fetches route data from the backend ( /api/ssr/... ). You can configure global loading and error placeholders for this lazy-load step:

app.set_loading_state(
    loadingComp=Page(Container(Text("Loading..."))),
    onErrorComp=Page(Container(Text("Failed to load route")))
)

These placeholders are rendered as static HTML (similar to SPA 404/403 pages), which means they do not register states/events and do not interfere with hydration.

Nested Layouts and Outlet(placeholder=...)

For nested routes, layouts typically include one or more Outlet placeholders. You can optionally render a layout-level placeholder inside an outlet:

Outlet(outlet_id="main", placeholder=Container(Text("Loading section...")))

This is useful when the parent layout is already visible and you want a placeholder only for the child region.


Development Workflow

Running in Development

You need two processes running simultaneously:

Terminal 1 - Frontend Dev Server:

dars dev
# Runs on http://localhost:8000
# Uses app.rTimeCompile() with hot reload for UI changes

Terminal 2 - Backend SSR Server:

dars dev
# Runs frontend and backend together when backendEntry is configured
# Uses backendEntry from dars.config.json (by default "backend.api:app")

Note: dars dev --backend is deprecated in v1.9.14; use dars dev instead.

How It Works

  1. Frontend Server (8000) : Serves the Dars preview (HTML/CSS/JS) and handles SPA routing.
  2. Backend Server (3000) : Renders SSR routes and provides API endpoints.

  3. Communication : Frontend fetches SSR content from backend via /api/ssr/*

Environment Detection

The DarsEnv class automatically configures URLs:

# Development
DarsEnv.get_urls()  {
    "backend": "http://localhost:3000",
    "frontend": "http://localhost:8000"
}

# Production
DarsEnv.get_urls()  {
    "backend": "/",
    "frontend": "/"
}

Authentication & Route Protection

Dars provides a secure, robust, and completely isolated authentication system. Tokens are kept strictly hidden from the VDOM in the browser using HttpOnly and SameSite cookies, rendering XSS theft impossible. It also integrates strict CSRF protection automatically.

How It Works

from dars.core.auth import requires_auth, requires_role

# Protect any FastAPI route — injects request.state.user
@app.get("/api/protected")
@requires_auth
async def protected_route(request: Request):
    return {"user": request.state.user}

# Role-based access control
@app.get("/api/admin")
@requires_role("admin")
async def admin_only(request: Request):
    return {"message": "Admin access granted"}

Quick SSR Auth Setup

def verify_user(username, password):
    if username == "admin" and password == "secret":
        return {"id": "1", "username": "admin", "role": "admin"}
    return None

@route("/dashboard", route_type=RouteType.SSR)
@requires_auth(verify_credentials_callback=verify_user, secret="super_secret")
async def dashboard(request: Request):
    user = request.state.user
    return Page(
        Text(f"Welcome, {user['username']}!"),
    )

For detailed guides and deep dives into Dars security mechanisms, see Authentication & Security .


SSR API Reference

create_ssr_app(dars_app, prefix="/api/ssr", streaming=False)

Creates a FastAPI application with automatic SSR endpoints.

Parameters: - dars_app (App): Your Dars application instance - prefix (str): URL prefix for SSR endpoints (default: /api/ssr ) - streaming (bool): When True , enables HTML streaming so the <head> and opening <body> are sent first, and the rest of the document is streamed afterwards. Default is False (classic non-streaming response).

Returns: - FastAPI application with registered SSR routes

Auto-generated Endpoints:

For each SSR route in your Dars app, create_ssr_app creates: - GET {prefix}/{route_name} - JSON payload used by the SPA router for lazy SSR loading - GET {route_path} - Full HTML SSR endpoint (e.g. / , /blog , /dashboard )

Additionally, if no SSR route takes / , a health-check endpoint is added at: - GET / - Returns basic JSON info about the SSR backend

Example (non-streaming):

from dars.backend.ssr import create_ssr_app

app = create_ssr_app(dars_app)
# Automatically creates, for example:
# - GET /api/ssr/index
# - GET /api/ssr/dashboard
# - GET /           (if root not taken by an SSR route)

Example (streaming enabled):

from dars.backend.ssr import create_ssr_app

app = create_ssr_app(dars_app, streaming=True)
# HTML responses for SSR routes are sent in two chunks:
# 1) <html> + <head> + opening <body>
# 2) The rest of the document (body content + scripts)

Behind the scenes, create_ssr_app uses SSRRenderer to: - Render the SSR route to HTML and wrap it in __dars_spa_root__ for hydration. - Build a minimal SPA config and expose it as window.__DARS_SPA_CONFIG__ . - Serialize initial state snapshots: - V1: window.__DARS_STATE__ (STATE_BOOTSTRAP) - V2: window.__DARS_STATE_V2__ (STATE_V2_REGISTRY via to_dict() ) - Inject a VDOM snapshot as window.__ROUTE_VDOM__ .

SSRRenderer

Low-level class for manual SSR rendering.

from dars.backend.ssr import SSRRenderer

renderer = SSRRenderer(dars_app)
result = renderer.render_route("dashboard", params={"user_id": "123"})

# Returns (simplified):
{
    "name": "dashboard",
    "html": "<div>...</div>",              # Body HTML for SPA hydration
    "fullHtml": "<!DOCTYPE html>...",      # Complete HTML document with <head>
    "scripts": [...],                       # Core + page-specific scripts
    "events": {...},                        # Event map for client-side binding
    "vdom": {...},                          # VDOM snapshot for the route
    "states": [...],                        # V1 state snapshot (STATE_BOOTSTRAP)
    "statesV2": [...],                      # V2 state snapshot (STATE_V2_REGISTRY)
    "spaConfig": {...},                     # Minimal SPA routing config
    "headMetadata": {...}                   # Metadata extracted from Head component
}
app = App(title="Hybrid App", ssr_url=ssr_url)

# SSR for landing page (SEO)
@route("/", route_type=RouteType.SSR)
def home():
    return Page(Text("Welcome!"))

# SPA for dashboard (interactive)
@route("/dashboard")
def dashboard():
    return Page(Text("Dashboard"))

# Static for docs (performance)
@route("/docs", route_type=RouteType.STATIC)
def docs():
    return Page(Text("Documentation"))

app.add_page("home", home(), title="Home", index=True)
app.add_page("dashboard", dashboard(), title="Dashboard")
app.add_page("docs", docs(), title="Docs")

Navigation Behavior: - SSR → SPA: Fetches from backend, hydrates - SPA → SPA: Client-side navigation (instant) - Any → Static: Loads pre-rendered HTML


Deployment

Production Configuration

1. Update Environment Mode:

# backend/apiConfig.py
class DarsEnv:
    MODE = "production"  # Change from "development"

2. Build Frontend:

dars build
# Generates static files in ./dist

3. Deploy Backend:

Your FastAPI backend serves both: - SSR-rendered pages via /api/ssr/* - Static files from ./dist

Deployment Platforms

Vercel / Netlify: - Deploy FastAPI backend as serverless function - Serve static files from CDN - Configure environment variables


Authentication & Security

Dars provides a production-grade, secure-by-default authentication system. Tokens are transported exclusively via HttpOnly cookies — completely isolated from the VDOM/JavaScript context, making XSS-based token theft impossible.

Table of Contents


Architecture Overview

Token Flow

┌─────────────────────────────────────────────┐
│                  Browser                      │
│                                               │
│  ┌───────────────┐   Cookies (HttpOnly)      │
│  │   Dars VDOM   │ ── dars_access_token ──►  │
│  │   (JS/HTML)   │ ── dars_refresh_token ─►  │
│  │               │ ── XSRF-TOKEN (read) ──►  │
│  │  Cannot read  │                            │
│  │  token values │                            │
│  └───────────────┘                            │
└──────────────────────┬────────────────────────┘
                       │ Auto cookie attach
                       ▼
┌──────────────────────────────────────────────┐
│            Dars Backend (FastAPI)              │
│                                                │
│  /_dars/auth/{id}/login    → Issue tokens     │
│  /_dars/auth/{id}/me       → User data        │
│  /_dars/auth/{id}/refresh  → Rotate tokens    │
│  /_dars/auth/{id}/logout   → Revoke session   │
│                                                │
│  @requires_auth  → Validates JWT, injects     │
│                     request.state.user        │
└──────────────────────────────────────────────┘
Cookie HttpOnly SameSite Secure Max-Age Purpose
dars_access_token Yes Strict Yes 15 min Short-lived JWT
dars_refresh_token Yes Strict Yes 7 days Long-lived opaque token
XSRF-TOKEN No Strict Yes 7 days CSRF protection

For multi-auth, cookie names are scoped: dars_access_token_{auth_id} , dars_refresh_token_{auth_id} .


Quick Start

1. Define a Verification Callback

The callback receives (username, password) and returns a user dict (must include "id" or "username" ) or None :

def verify_user(username, password):
    if username == "admin" and password == "secret":
        return {"id": "1", "username": "admin", "role": "admin"}
    return None

Supports both sync and async callbacks.

2. Register Auth with the App

from dars.all import *

app = App(title="My Secure App")
app.setup_auth(
    verify_credentials_callback=verify_user,
    secret="your-secret-key-change-in-production"
)

3. Protect a Route

Use @requires_auth below @route for SSR/API routes that need authentication. The decorator automatically injects request.state.user :

@route("/dashboard", route_type=RouteType.SSR)
@requires_auth
async def dashboard(request: Request):
    user = request.state.user  # injected by @requires_auth
    return Page(
        Text(f"Welcome, {user['username']}!"),
    )

4. Build a Login Page

@route("/login", route_type=RouteType.SSR)
def login_page():
    login_form = collect_form(username=V("#username"), password=V("#password"))
    validator = FormValidator({"username": [required()], "password": [required()]})

    on_success = redirect_after_login("/dashboard")
    submit = validator.validated_submit(
        url="/_dars/auth/login",
        form_data=login_form,
        on_success=on_success,
        on_error=setText(".error", "Invalid credentials"),
    )

    return Page(
        Container(
            Text("Login", style="text-2xl font-bold"),
            Input(id="username", placeholder="Username"),
            Input(id="password", placeholder="Password", type="password"),
            Text("", class_name="error", style="color: red;"),
            Button("Login", on_click=submit),
        )
    )

Three Ways to Configure Auth

Place @requires_auth directly below @route . You can optionally pass verify_credentials_callback and secret to auto-register a custom auth scheme:

def verify_guest(username, password):
    if username == "guest" and password == "guest":
        return {"id": "g1", "username": "guest", "role": "guest"}
    return None

@route("/about", route_type=RouteType.SSR)
@requires_auth(verify_credentials_callback=verify_guest, secret="about_secret")
def about():
    # Auto-registers auth config with auth_id = "auth_about"
    # Endpoints: /_dars/auth/auth_about/login, /me, /logout, /refresh
    return Page(...)

When used without arguments ( @requires_auth as bare decorator), it uses the default auth configuration (configured via app.setup_auth() ).

2. app.setup_auth() (Global)

Register one or more global auth configurations:

app = App(title="My App")
app.setup_auth(verify_credentials_callback=verify_admin, secret="admin_secret", auth_id="admin")
app.setup_auth(verify_credentials_callback=verify_user, secret="user_secret", auth_id="default")

3. register_auth_config() (Programmatic)

For advanced use cases, register directly:

from dars.backend.auth_routes import register_auth_config

register_auth_config(verify_callback, secret, auth_id="custom")

Route Guards & Route Types

Dars provides two layers of route protection:

Layer 1: Route Types (Client-Side Guards)

The @route decorator supports RouteType for client-side routing guards:

from dars.core.route_types import RouteType

# Public — no auth required (default)
@route("/")

# SSR — server-side rendered, no auth
@route("/blog", route_type=RouteType.SSR)

# Private — requires authentication, redirects to login if not auth'd
@route("/account", route_type=RouteType.PRIVATE)

# Protected — requires auth + specific role
@route("/admin", route_type=RouteType.PROTECTED, roles=["admin"])

These guards work at the SPA router level — unauthenticated users are redirected to /login (or custom redirect path) without the protected component ever loading.

Layer 2: @requires_auth (Server-Side Enforcement)

For SSR routes and API endpoints, the @requires_auth decorator validates the JWT on every request:

from dars.core.auth import requires_auth, requires_role

# Protect any FastAPI route
@app.get("/api/protected")
@requires_auth
async def protected_route(request: Request):
    return {"user": request.state.user}

# Role-based access control
@app.get("/api/admin")
@requires_role("admin")
async def admin_only(request: Request):
    return {"message": "Admin access granted"}

Combining Both Layers

For maximum security, combine client-side guards with server-side enforcement:

@route("/admin", route_type=RouteType.PROTECTED, roles=["admin"])
@requires_auth(verify_credentials_callback=verify_admin, secret="admin_secret")
async def admin_panel(request: Request):
    user = request.state.user
    return Page(...)

Multi-Auth: Multiple Isolated Schemes

Dars supports running multiple independent authentication schemes simultaneously, each with its own:

  • Verification callback
  • Secret key
  • Session store
  • Scoped cookies (identified by auth_id suffix)
Auth ID Access Cookie Refresh Cookie
"default" dars_access_token dars_refresh_token
"admin" dars_access_token_admin dars_refresh_token_admin
"auth_about" dars_access_token_auth_about dars_refresh_token_auth_about

A user can be simultaneously logged in with different identities in different sections of the same app without any session collision.


Building Auth Pages

Since the frontend compiles to pure HTML/CSS/JS, you use Dars native APIs to interact with the auth system:

Checking Session on Page Load

fetch_me, *_ = useFetch(
    "/_dars/auth/me",
    method="GET",
    on_success=runSequence(
        updateVRef(".show-dashboard", True),
        updateVRefFromResponse(".user-name", key="response.user.username"),
    ),
    on_error=runSequence(updateVRef(".show-login", True)),
)
page.add_script(fetch_me)

For multi-auth, scope the endpoint:

fetch_me, *_ = useFetch("/_dars/auth/admin/me", method="GET", ...)

Login with Form Validation

login_form = collect_form(username=V("#username"), password=V("#password"))
validator = FormValidator({"username": [required()], "password": [required()]})

submit = validator.validated_submit(
    url="/_dars/auth/login",
    form_data=login_form,
    on_success=redirect_after_login("/dashboard"),
    on_error=runSequence(updateVRefFromResponse(".error", key="response.detail")),
)

Logout

fetch_logout, *_ = useFetch("/_dars/auth/logout", method="POST",
    on_success=runSequence(updateVRef(".show-dashboard", False), updateVRef(".show-login", True)))

Middleware Reference

AuthMiddleware

Validates JWT on every request (except excluded paths). Injects request.state.user :

from dars.backend.middleware import AuthMiddleware

app.add_middleware(
    AuthMiddleware,
    secret="your-secret",
    exclude_paths=["/_dars/auth", "/api/public", "/docs"],
    csrf_protection=True,
)

SecurityHeadersMiddleware

Injects security headers into every response:

from dars.backend.middleware import SecurityHeadersMiddleware

app.add_middleware(
    SecurityHeadersMiddleware,
    csp="default-src 'self'",
    hsts=True,
)

Default headers: X-Content-Type-Options , X-Frame-Options , X-XSS-Protection , Referrer-Policy , Permissions-Policy .


API Reference

DarsAuth.encode_token(payload, secret, algorithm="HS256", expires_in=3600)

JWT encoding with HMAC-SHA256. Returns token string.

DarsAuth.decode_token(token, secret, algorithm="HS256")

Validate JWT, return payload dict. Raises ValueError on failure/expiry.

DarsAuth.hash_password(password) / DarsAuth.verify_password(password, hashed)

PBKDF2-SHA256 password hashing (100k iterations) with timing-attack resistant comparison.

DarsAuth.set_auth_cookies(response, access_token, refresh_token, xsrf_token, auth_id="default")

Sets HttpOnly secure cookies with scoped names.

DarsAuth.clear_auth_cookies(response, auth_id="default")

Clears all auth cookies.

@requires_auth(verify_credentials_callback=None, secret=None, auth_id=None)

Decorator for FastAPI route handlers. Validates JWT, injects request.state.user . Auto-registers auth config when callback and secret are provided.

@requires_role(role)

Decorator for role-based access control. Requires @requires_auth or middleware to have set request.state.user first.

app.setup_auth(verify_credentials_callback, secret, auth_id="default", login_page="/login")

Register a global auth configuration.

register_auth_config(verify_credentials_callback, secret, auth_id)

Programmatic auth config registration.

get_auth_config(auth_id)

Retrieve an auth configuration by ID.

Native Auth Endpoints

When auth is configured, Dars automatically exposes:

Method Path Description
POST /_dars/auth/{auth_id}/login Validate credentials, issue session
POST /_dars/auth/{auth_id}/refresh Rotate tokens using refresh token
POST /_dars/auth/{auth_id}/logout Revoke session, clear cookies
GET /_dars/auth/{auth_id}/me Return authenticated user data

For the default scheme ( auth_id="default" ), the path simplifies to /_dars/auth/login , etc.

Session Management

from dars.backend.session import SessionManager, InMemorySessionStore

store = InMemorySessionStore()
manager = SessionManager(store)

token = manager.issue_refresh_token(user_id, payload)
session = manager.validate_refresh_token(token)
manager.revoke_refresh_token(token)

The SessionStore protocol allows custom implementations for Redis, database-backed sessions, etc.

State Management in Dars

Dars Framework features powerful state management systems , designed for different use cases:

State V2 - Dynamic State Management

Modern, Pythonic state management for reactive UIs.

Quick Start

from dars.all import *

# Create a component
display = Text("0", id="counter")

# Create state
counter = State(display, text=0)

# Use reactive properties
increment_btn = Button("Increment", on_click=counter.text.increment(by=1))
decrement_btn = Button("Decrement", on_click=counter.text.decrement(by=1))
reset_btn = Button("Reset", on_click=counter.reset())

Core Concepts

State Class

The State class wraps a component and provides reactive property access.

from dars.all import State

display = Text("0", id="counter")
counter_state = State(display, text=0)

Constructor Parameters:

  • component : The component to manage (can be a component object or string ID)
  • **default_props : Default property values (e.g., text=0 , style="..." )

State with String IDs (for Dynamic Components)

State() can accept either a component object or a string ID. This is useful for components created dynamically:

from dars.all import *
from dars.backend import createComp

# Traditional: State with component object
existing_text = Text("0", id="counter")
existing_state = State(existing_text, text=0)

# New: State with string ID (for components created later)
dynamic_state = State("dynamic-counter", text=0)

# Create the component later
create_btn.on_click = createComp(
    target=Text("0", id="dynamic-counter"),
    root="container-id"
)

# State works even though component was created after state!
increment_btn.on_click = dynamic_state.text.increment(by=1)

Use Cases:

  • Components created with createComp()
  • Dynamically generated UIs
  • Conditional component rendering
  • Server-side rendered components

Reactive Properties

Access component properties through the state object to get reactive operations:

# Increment/decrement numeric properties
counter.text.increment(by=1)
counter.text.decrement(by=2)

# Set property values
counter.text.set(value=100)

# Auto operations (continuous)
counter.text.auto_increment(by=1, interval=1000)  # +1 every second
counter.text.auto_decrement(by=1, interval=500)   # -1 every 500ms
counter.text.stop_auto()  # Stop auto operations

Reset to Defaults

The reset() method restores all properties to their initial values:

state = State(display, text=0, style="text-blue-500")

# ... user modifies the component ...

# Reset everything back to initial state
reset_btn.on_click = state.reset()

Reactive Operations

Increment and Decrement

# Increment by 1 (default)
button.on_click = counter.text.increment()

# Increment by custom amount
button.on_click = counter.text.increment(by=5)

# Decrement (negative increment)
button.on_click = counter.text.decrement(by=1)
# OR
button.on_click = counter.text.increment(by=-1)
button.on_click = counter.text.set(value=0)

All Property Types Supported

State V2 supports updating all component properties , not just text:

Text Content:

state.text.set("New text")

HTML Content:

state.html.set("<strong>Bold text</strong>")

CSS Styles:

state.style.set("text-red-500 fs-[24px]")

CSS Classes:

# Set class name
state.style.set("active")

Event Handlers:

# Update event handler dynamically
state.update(on_click=alert("New handler!"))

# Or with dScript
from dars.scripts.dscript import dScript
state.update(on_click=log('clicked'))

Multiple Properties at Once:

state.update(
    text="Updated!",
    style="text-green-500",
    on_click=alert("Done!")
)

Auto Operations

Auto operations create continuous reactive updates:

# Auto-increment timer
timer = State(display, text=0)

start_btn.on_click = timer.text.auto_increment(by=1, interval=1000)
stop_btn.on_click = timer.text.stop_auto()

With Limits:

# Auto-increment up to 100
timer.text.auto_increment(by=1, interval=1000, max=100)

# Auto-decrement down to 0
countdown.text.auto_decrement(by=1, interval=1000, min=0)

Backend Integration with useData()

State V2 integrates seamlessly with Dars backend HTTP utilities for reactive API-driven UIs:

from dars.all import *
from dars.backend import get, useData

# Create components
user_name = Text("", id="user-name")
user_email = Text("", id="user-email")

# Create states
name_state = State(user_name, text="")
email_state = State(user_email, text="")

# Fetch and bind API data - pure Python!
fetch_btn = Button(
    "Load User",
    on_click=get(
        id="userData",
        url="https://api.example.com/users/1",
        # Access nested data with dot notation
        callback=(
            name_state.text.set(useData('userData').name)
            .then(email_state.text.set(useData('userData').email))
        )
    )
)

Key Features:

  • useData('id') - Access fetched data by operation ID
  • Dot notation - useData('userData').name accesses nested properties
  • .then() chaining - Chain multiple state updates sequentially
  • No JavaScript - Everything is pure Python

Complete Example

from dars.all import *

app = App("State V2 Demo")

# Timer display
timer_display = Text("0", id="timer", style="text-4xl")
timer = State(timer_display, text=0)

# Status display
status = Text("Paused", id="status")
status_state = State(status, text="Paused", style="paused")

# Control buttons
start_btn = Button("Start",
    on_click=timer.text.auto_increment(by=1, interval=1000)
)
stop_btn = Button("Stop",
    on_click=[
        timer.text.stop_auto(),
        status_state.update(text="Paused", style="paused")
    ]
)
reset_btn = Button("Reset",
    on_click=[
        timer.text.stop_auto(),
        timer.reset(),
        status_state.reset()
    ]
)

page = Page(Container(timer_display, status, start_btn, stop_btn, reset_btn))
app.add_page("index", page, index=True)

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

Dynamic State Updates & this()

Dars introduces dynamic state updates, allowing you to modify component properties directly without pre-registering state indices.

this() helper

The this() helper allows a component to refer to itself in an event handler and apply updates dynamically.

from dars.core.state import this

btn = Button("Click me", on_click=this().state(text="Clicked!", style="text-red-500"))

Supported dynamic properties:

  • text : Update text content.
  • html : Update inner HTML.
  • style : Dictionary of CSS styles.
  • attrs : Dictionary of attributes.
  • classes : Dictionary with add , remove , or toggle (single string or list of strings).
this().state(
    text="Updated",
    style="bg-slate-100",
    classes={"add": ["active"], "remove": ["inactive"]}
)

Using Raw JavaScript Values ( RawJS )

You can pass raw JavaScript variables to dynamic updates using RawJS . This is particularly useful when:

  • Chaining scripts where a previous script returns a value
  • Working with async operations like file reading
  • Using dScript.ARG to reference values from previous scripts
from dars.scripts.dscript import RawJS, Arg

# Example: update component with the result from a previous action
update_op = this().state(text=Arg)

chained = some_async_action.then(update_op)

# Using custom JavaScript expressions
this().state(text=RawJS("someVar + ' processed'"))

Best Practices

Choose State V2 When:

  • Building simple counters or timers
  • Need auto-increment/decrement
  • Want quick reactive updates
  • Working with single components

Use this() When:

  • Don't need state tracking
  • Making one-off updates
  • Working with async operations
  • Targeting the clicked element

Hooks System

Dars Framework introduces a Hooks system inspired by React, enabling reactive and stateful behavior in both FunctionComponents and built-in components.

The reactivity system in Dars is divided into the following sections:

  1. Pythonic Value Helpers : The V() helper and expression system used throughout all hooks.
  2. Component-Level State (VRefs) : Lightweight, fast, and DOM-bound reactive state.
  3. Global Application State : Structured State objects for shared application logic.
  4. Forms & Validation : Hooks for form collection and client-side validation.
  5. Network & Async Operations : Data fetching and chained actions.
  6. Best Practices : Guidelines and tips.

1. Pythonic Value Helpers

Dars provides a set of helpers to make working with DOM values and reactive state completely Pythonic, eliminating the need for raw JavaScript.

V() - Value Reference

The V() helper allows you to extract values from DOM elements (via CSS selectors) or reactive state (via state paths).

CSS Selectors (DOM Elements)

from dars.all import *

# Select by ID
V("#myInput")

# Select by Class
V(".myClass")

State Paths (Reactive State)

V() also supports extracting values directly from reactive state created by useDynamic() (covered in detail in the Global Application State section below):

# Extract from reactive state
V("cart.total")      # Gets current value of cart.total
V("user.name")       # Gets current value of user.name
V("product.price")   # Gets current value of product.price

How it works:

  • V("cart.total") finds the reactive element created by useDynamic("cart.total")
  • Reads its current textContent value
  • Perfect for combining reactive state with calculations

Transformations

You can chain transformation methods to process values before using them:

# String transformations
V("#name").upper()   # "JOHN"
V("#name").lower()   # "john"
V("#name").trim()    # Remove whitespace

# Numeric transformations (required for math operations!)
V("#age").int()      # 25 (integer)
V("#price").float()  # 19.99 (float)
V("cart.total").float()  # Extract state value as float

Operations

V() now supports declarative mathematical expressions with operator overloading!

# Simple arithmetic
calc.result.set(V(".a").float() + V(".b").float())

# Complex expressions with automatic precedence
calc.result.set(
    (V(".a").float() + V(".b").float()) * V(".c").float()
)

# Dynamic operators from Select elements
calc.result.set(
    V(".num1").float() + V(".operation").operator() + V(".num2").float()
)

Features:

  • Operator overloading ( + , - , * , / , % , ** )
  • Automatic operator precedence
  • Dynamic operators from Select/Input
  • NaN validation with console warnings
  • Type safety (numeric ops require .float() or .int() )

[!TIP] For complete documentation on mathematical operations, operator precedence, dynamic operators, and advanced examples, see the Mathematical Operations docs.

equal() helper

Sometimes you want to normalize a value (literal or expression) to safely combine it within an expression with V() without worrying about precedence or operators:

from dars.hooks.value_helpers import V, equal

# Add 1 using V() + literal
updateVRef(".dyn_count", V(".dyn_count").int() + 1)

# Normalize a literal as a mathematical expression
updateVRef(".dyn_count", equal(0))           # forces to 0

# Combine with another expression based on V()
expr = V(".a").int() + equal(V(".b").int())
updateVRef(".result", expr)
  • equal(value) wraps the value in a MathExpression , so it integrates into the same operation tree as V() and respects the async/NaN-safe semantics of the expression system.

Complete Example

from dars.all import *

app = App("Shopping Cart")

# Reactive state
cartState = State("cart", total=0.0)
productState = State("product", name="Widget", price=19.99, quantity=1)

@FunctionComponent
def ProductCard(**props):
    return f'''
    <div {Props.id} {Props.class_name} {Props.style}>
        <!-- Reactive display -->
        <h3>{useDynamic("product.name")}</h3>
        <p>Price: ${useDynamic("product.price")}</p>

        <!-- Editable quantity with selector -->
        <input type="number"
               value="{useValue("product.quantity", ".qty-input")}"
               min="1" />

        <!-- Reactive total -->
        <p>Total: ${useDynamic("cart.total")}</p>
    </div>
    '''

@route("/")
def index():
    return Page(
        ProductCard(id="product-card", name="Milk", price=100, quantity=2, total=0),

        # Calculate: DOM input × State value
        Button("Calculate Total", on_click=cartState.total.set(
            V(".qty-input").int() * V("product.price").float()
        )),

        # String concatenation (no transformation needed)
        Button("Show Info", on_click=productState.name.set(
            "Product: " + V("product.name") + " - $" + V("product.price")
            )
        )
    )

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

# Watch for changes
app.useWatch("cart.total", log("Cart total changed!"))

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

url() - URL Builder

The url() helper constructs dynamic URLs by interpolating ValueRef objects into a template string.

# Generates: https://api.example.com/users/123/profile
fetch(
    url("https://api.example.com/users/{id}/profile", id=V("#userId"))
)

# With state values
fetch(
    url("/api/products/{id}", id=V("product.id"))
)

# Mixed
fetch(
    url("/api/{resource}/{id}",
        resource="users",
        id=V("#userId"))
)

Note: Use standard Python format string syntax {key} for placeholders.

Boolean & Comparison Operators

V() supports boolean and comparison operations, enabling declarative validation and conditional logic without raw JavaScript!

Comparison Operators

Compare values using Python-style operators:

from dars.all import *

# Numeric comparisons (require .int() or .float())
V("#age").int() >= 18
V("#price").float() < 100.0
V("#quantity").int() == 5

# String equality
V("#password") == V("#confirm-password")
V("#email") != ""

# All operators: ==, !=, >, <, >=, <=

String Methods

Check string properties with built-in methods:

# Check if string contains substring
V("#email").includes("@")

# Check string start/end
V("#filename").startswith("report_")
V("#filename").endswith(".pdf")

# Get string length (returns ValueRef with .int())
V("#password").length() >= 8

# Convert to boolean
V("#checkbox").bool()

Logical Operators

Combine boolean expressions with .and_() and .or_() :

# AND operator
(V("#age").int() >= 18).and_(V("#age").int() <= 65)

# OR operator
(V("#email").includes("@")).or_(V("#phone").length() >= 10)

# Complex combinations
(V("#name").length() >= 3).and_(
    (V("#email").includes("@")).and_(
        V("#email").includes(".")
    )
)

Conditional Expressions

Use .then() for ternary operations (condition ? trueVal : falseVal):

# Simple conditional
(V("#age").int() >= 18).then("Adult", "Minor")

# With state updates
state.message.set(
    (V("#score").int() >= 60).then("Pass", "Fail")
)

# Nested conditionals
(V("#premium").bool()).then("10% discount", "No discount")

# Complex validation
state.validation.set(
    (V("#password").length() >= 8).and_(
        V("#password") == V("#confirm")
    ).then("✓ Valid", "✗ Invalid")
)

Complete Validation Example

from dars.all import *

app = App("Form Validation")
form = State("form",
    email_valid="",
    age_valid="",
    password_valid=""
)

@route("/")
def index():
    return Page(
        Container(
            # Email validation
            Input(id="email", placeholder="Email"),
            Button(
                "Validate Email",
                on_click=form.email_valid.set(
                    (V("#email").includes("@")).and_(
                        V("#email").includes(".")
                    ).then("✓ Valid email", "✗ Invalid email")
                )
            ),
            Text(text=useDynamic("form.email_valid")),

            # Age validation
            Input(id="age", input_type="number", placeholder="Age"),
            Button(
                "Validate Age",
                on_click=form.age_valid.set(
                    (V("#age").int() >= 18).and_(
                        V("#age").int() <= 120
                    ).then("✓ Valid age", "✗ Must be 18-120")
                )
            ),
            Text(text=useDynamic("form.age_valid")),

            # Password match validation
            Input(id="password", input_type="password", placeholder="Password"),
            Input(id="confirm", input_type="password", placeholder="Confirm"),
            Button(
                "Check Match",
                on_click=form.password_valid.set(
                    (V("#password") == V("#confirm")).and_(
                        V("#password").length() >= 8
                    ).then("✓ Passwords match", "✗ Passwords don't match")
                )
            ),
            Text(text=useDynamic("form.password_valid"))
        )
    )

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

getDateTime() - Timestamp Helper

**Generate client-side timestamps for forms and state updates.

Basic Usage

from dars.all import *

# Default ISO format
getDateTime()  # "2025-12-04T22:04:09.123Z"

# Different formats
getDateTime("iso")        # "2025-12-04T22:04:09.123Z"
getDateTime("locale")     # "12/4/2025, 10:04:09 PM"
getDateTime("date")       # "12/4/2025"
getDateTime("time")       # "10:04:09 PM"
getDateTime("timestamp")  # 1733362449123

Usage in Forms

# Add timestamp to form submission
form_data = collect_form(
    name=V("#name"),
    email=V("#email"),
    submitted_at=getDateTime()  # ISO format
)

# Different timestamp formats
form_data = collect_form(
    name=V("#name"),
    created_at=getDateTime("iso"),
    display_date=getDateTime("locale"),
    date_only=getDateTime("date"),
    time_only=getDateTime("time"),
    unix_timestamp=getDateTime("timestamp")
)

Usage with State

# Update state with current timestamp
Button("Save", on_click=state.last_updated.set(getDateTime()))

# Different formats
Button("Save Date", on_click=state.date.set(getDateTime("date")))
Button("Save Time", on_click=state.time.set(getDateTime("time")))

Complete Example

from dars.all import *

app = App("Timestamp Demo")
state = State("state", last_action="", timestamp="")

form_data = collect_form(
    action=V("#action"),
    timestamp=getDateTime("locale")
)

@route("/")
def index():
    return Page(
        Container(
            Input(id="action", placeholder="What did you do?"),

            Button(
                "Record Action",
                on_click=form_data.to_state(state.last_action)
            ),

            Text("Last Action:", style="font-bold"),
            Text(text=useDynamic("state.last_action")),

            Button(
                "Update Timestamp",
                on_click=state.timestamp.set(getDateTime("locale"))
            ),

            Text("Current Time:", style="font-bold"),
            Text(text=useDynamic("state.timestamp"))
        )
    )

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

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

2. Component-Level State (VRef System)

The VRef system allows you to define and manage state declaratively without creating complex global objects. It is the recommended approach for local component state.

setVRef() - Independent Value Reference

The setVRef() hook allows you to define initial values that are tied to a specific CSS selector. This is the foundation for creating component-level state that can be shared across multiple components without using global State objects.

Basic Usage

Create a reference with an initial value and a selector, then pass it to components.

from dars.all import *

# Create a reference tied to the "#count" ID
count_ref = setVRef(0, "#count")

# Use it in a component
Text(count_ref, id="count")

Shared Values (Multi-Component Updates)

By using a class selector , you can share the same value across multiple components and update them all simultaneously!

# Create a reference tied to a CLASS selector
price_ref = setVRef(99.99, ".product-price")

# Use in multiple places
Container(
    Text("Price: $", style="font-bold"),
    Text(price_ref, class_name="product-price"),  # Main display

    Container(
        Text("Also shown here: $"),
        Text(price_ref, class_name="product-price")   # Secondary display
    )
)

# Update ALL elements matching ".product-price" at once
Button("Discount", on_click=updateVRef(".product-price", 49.99))

Usage in FunctionComponents

setVRef works seamlessly with @FunctionComponent . The value is resolved internally, so your templates remain clean.

@FunctionComponent
def UserBadge(name_ref, **props):
    return f'''
    <div {Props.class_name} {Props.style}>
        User: <span class="user-name">{name_ref}</span>
    </div>
    '''

# Define ref
user_ref = setVRef("Guest", ".user-name")

# Render
UserBadge(user_ref, class_name="badge")

# Update
Button("Login", on_click=updateVRef(".user-name", "John Doe"))

Syntax

setVRef(initial_value: Any, selector: str) -> VRefValue

Parameters:

  • initial_value : The initial value to display (string, number, boolean).
  • selector : The CSS selector (ID or Class) that identifies the element(s).
    • Use #id for single elements.
    • Use .class for multiple elements sharing the value.

Returns:

  • VRefValue : An object representing the value, ready to be passed to components.

useVRef() - Consume VRef State

The useVRef() hook allows you to create reactive bindings between setVRef values and your UI. It seamlessly works with the V() value extractor and mathematical expressions.

Basic Usage

Use it to wrap V() expressions directly into component properties:

from dars.all import *

# Define the state globally or locally
price = setVRef(10.0, ".item-price")

# Consume the state reactively
Container(
    Text("Price: $"),
    Text(text=useVRef(V(".item-price")))
)

Advanced Expressions

useVRef() truly shines when combined with mathematical and boolean operations. It automatically tracks dependencies and updates the UI instantly:

# Create basic states
setVRef(19.99, ".price")
setVRef(2, ".quantity")

# Reactive mathematical expression
Text(
    text=useVRef(V(".price").float() * V(".quantity").int())
)

# Reactive boolean expressions
Button(
    "Checkout",
    disabled=useVRef(V(".quantity").int() == 0)
)

Auto-Dependency Detection

You do not need to specify which selectors to watch. The compiler automatically walks the V() expression tree and extracts every CSS selector used. At runtime, whenever any of those selectors is updated via updateVRef() , the binding re-evaluates and patches the DOM.

# The framework auto-detects that this binding depends on ".price" and ".qty"
Text(text=useVRef(V(".price").float() * V(".qty").int()))

# When either changes, the text updates automatically:
Button("Double price", on_click=updateVRef(".price", V(".price").float() * 2))
Button("+1 qty",       on_click=updateVRef(".qty",   V(".qty").int() + 1))

If you need to override auto-detection, pass explicit selectors via the dependencies parameter:

Text(text=useVRef(
    V(".total").float(),
    dependencies=[V(".price"), V(".qty")]   # explicit override
))

Reactive Callbacks

Pass dScript , RawJS , or plain strings via the callbacks parameter. They fire every time the binding re-evaluates, enabling side-effects like logging or chained updates.

from dars.scripts.dscript import RawJS

# Single callback (no list needed)
Text(text=useVRef(
    V(".value-stuff"),
    callbacks=RawJS("console.log('value-stuff changed!');")
))

# dScript callback
Text(text=useVRef(
    V(".score").int(),
    callbacks=log("Score was updated")
))

# Multiple callbacks (list)
Text(text=useVRef(
    V(".total").float(),
    callbacks=[
        log("total changed"),
        RawJS("document.title = 'Total: ' + document.querySelector('.total-display').textContent;")
    ]
))

Syntax

useVRef(
    vexpr: Union[ValueRef, MathExpression, BooleanExpression, Any],
    dependencies: Optional[List[ValueRef]] = None,
    callbacks: Optional[Union[dScript, RawJS, str, List[Union[dScript, RawJS, str]]]] = None
) -> VRefBinding

Parameters:

  • vexpr : A V() expression, an operator chain, or a direct literal value.
  • dependencies : Optional explicit list of V() selectors to watch. If omitted, selectors are auto-detected from the expression tree.
  • callbacks : Optional callback(s) fired every time the binding re-evaluates. Accepts a single dScript , RawJS , or str , or a list of those.

Returns:

  • VRefBinding : An object that resolves to the current value on render, and injects JavaScript to update reactively on hydration.

updateVRef() - Component-Level State Updates

Update DOM element values declaratively without State objects!

The updateVRef() function completes the component-level state management cycle, providing a Pythonic way to update values alongside V() for reading and boolean operators for validation.

The Complete Cycle:

  1. Read : V("#input") - Extract values
  2. Validate : V("#input").length() >= 3 - Boolean validation
  3. Update : updateVRef("#input", "new value") - Update values ✨ NEW!

Basic Usage

from dars.all import *

# Update text content
Button("Set Name", on_click=updateVRef("#name", "John Doe"))

# Update input value
Button("Clear Email", on_click=updateVRef("#email", ""))

# Update checkbox
Button("Check Box", on_click=updateVRef("#agree", True))

# Update number
Button("Set Price", on_click=updateVRef("#price", 99.99))

With V() Expressions

Combine updateVRef() with V() expressions for dynamic updates:

# Copy values between elements
Button("Copy", on_click=updateVRef("#target", V("#source")))

# With transformations
Button("Uppercase", on_click=updateVRef("#output", V("#input").upper()))

# With calculations
Button("Calculate Total", on_click=updateVRef("#total",
    V("#price").float() * V("#qty").int()
))

# With string concatenation
Button("Generate Full Name", on_click=updateVRef("#full-name",
    V("#first-name") + " " + V("#last-name")
))

With Boolean Expressions

Use boolean operators for conditional updates:

# Conditional text based on age
Button("Check Age", on_click=updateVRef("#status",
    (V("#age").int() >= 18).then("Adult", "Minor")
))

# Validation messages
Button("Validate Email", on_click=updateVRef("#message",
    (V("#email").includes("@")).and_(
        V("#email").includes(".")
    ).then("✓ Valid email", "✗ Invalid email")
))

# Complex validation
Button("Check Password", on_click=updateVRef("#pwd-status",
    (V("#password").length() >= 8).and_(
        V("#password") == V("#confirm")
    ).then("✓ Passwords match", "✗ Passwords don't match")
))

Batch Updates

Update multiple elements with a single call:

# Clear entire form
Button("Clear All", on_click=updateVRef({
    "#name": "",
    "#email": "",
    "#age": "",
    "#phone": ""
}))

# Fill sample data
Button("Fill Sample Data", on_click=updateVRef({
    "#name": "John Doe",
    "#email": "john@example.com",
    "#age": 25,
    "#phone": "555-0123"
}))

# Mix literals and expressions
Button("Update All", on_click=updateVRef({
    "#full-name": V("#first") + " " + V("#last"),
    "#email-lower": V("#email").lower(),
    "#age-status": (V("#age").int() >= 18).then("Adult", "Minor")
}))

Complete Examples

Example 1: Counter (No State Object!)
from dars.all import *

app = App("Counter Demo")

@route("/")
def index():
    return Page(
        Container(
            # Display count
            Text("Count: ", style="font-bold"),
            Text("0", id="count", style="text-[48px] font-bold text-blue-600"),

            # Update buttons
            Container(
                Button(
                    "+",
                    on_click=updateVRef("#count", V("#count").int() + 1),
                    style="bg-green-500 text-white px-6 py-3 rounded"
                ),
                Button(
                    "-",
                    on_click=updateVRef("#count", V("#count").int() - 1),
                    style="bg-red-500 text-white px-6 py-3 rounded"
                ),
                Button(
                    "Reset",
                    on_click=updateVRef("#count", 0),
                    style="bg-gray-500 text-white px-6 py-3 rounded"
                ),
                style="flex gap-2"
            )
        )
    )

app.add_page("index", index())
Example 2: Form Auto-Fill
@route("/form")
def form():
    return Page(
        Container(
            Input(id="first-name", placeholder="First Name"),
            Input(id="last-name", placeholder="Last Name"),
            Input(id="full-name", placeholder="Full Name", readonly=True),

            # Auto-generate full name
            Button(
                "Generate Full Name",
                on_click=updateVRef("#full-name",
                    V("#first-name") + " " + V("#last-name")
                )
            ),

            # Normalize inputs
            Button(
                "Normalize All",
                on_click=updateVRef({
                    "#first-name": V("#first-name").trim(),
                    "#last-name": V("#last-name").trim()
                })
            ),

            # Clear all
            Button(
                "Clear All",
                on_click=updateVRef({
                    "#first-name": "",
                    "#last-name": "",
                    "#full-name": ""
                })
            )
        )
    )
Example 3: Shopping Cart
@route("/cart")
def cart():
    return Page(
        Container(
            Input(id="price", input_type="number", value="19.99", placeholder="Price"),
            Input(id="quantity", input_type="number", value="1", placeholder="Quantity"),

            Text("Total: $", style="font-bold"),
            Text("0", id="total", style="text-[24px] text-green-600"),

            # Calculate total
            Button(
                "Calculate Total",
                on_click=updateVRef("#total",
                    V("#price").float() * V("#quantity").int()
                )
            ),

            # Apply discount
            Button(
                "Apply 10% Discount",
                on_click=updateVRef("#total",
                    V("#total").float() * 0.9
                )
            ),

            # Reset
            Button(
                "Reset",
                on_click=updateVRef({
                    "#price": "19.99",
                    "#quantity": "1",
                    "#total": "0"
                })
            )
        )
    )

Syntax

updateVRef(selector, value) -> dScript
updateVRef(dict) -> dScript

Parameters:

  • selector : CSS selector string (e.g., "#id" , ".class" )
  • value : Value to set - can be:
    • Literal: "text" , 42 , True
    • V() expression: V("#source")
    • Transformation: V("#input").upper()
    • Math expression: V("#a").int() + V("#b").int()
    • Boolean expression: (V("#age").int() >= 18).then("Adult", "Minor")
  • dict : Dictionary of {selector: value} pairs for batch updates

Returns:

  • dScript object for use in event handlers

Supported Elements:

  • Input / Textarea : Updates .value property
  • Checkbox / Radio : Updates .checked property
  • Select : Updates .value property
  • Other elements: Updates .textContent

Integration with Other Features

With collect_form()
# Normalize before collecting
form_data = collect_form(
    name=V("#name"),
    email=V("#email")
)

Button(
    "Normalize & Submit",
    on_click=sequence(
        updateVRef({
            "#name": V("#name").trim(),
            "#email": V("#email").lower().trim()
        }),
        form_data.submit("http://localhost:3000/submit")
    )
)
With State (Hybrid Approach)
# Local updates for preview
Button("Preview", on_click=updateVRef("#preview",
    "Name: " + V("#name") + ", Email: " + V("#email")
))

# Save to global state
user = State("user", name="", email="")
Button("Save to State", on_click=sequence(
    user.name.set(V("#name")),
    user.email.set(V("#email"))
))

When to Use updateVRef() vs State.set()

Use updateVRef() when:

  • Updating UI elements temporarily
  • Form auto-fill and normalization
  • Local calculations and previews
  • Component-level state
  • You don't need reactivity across components

Use State.set() when:

  • Data needs to persist
  • Multiple components need the value
  • You need automatic reactivity
  • Application-level state

Use both (Hybrid):

  • Local updates for immediate feedback
  • State updates for persistence
  • Best of both worlds!

Global Application State

For state that needs to be shared across many disconnected components or persists across navigation, use State objects.

useDynamic() - Reactive State Binding

The useDynamic() hook creates reactive bindings between external State objects and component properties.

1. Usage in Built-in Components

You can pass useDynamic() directly to properties of built-in components like Text , Button , Input , etc.

from dars.all import *

# Create state
userState = State("user", name="John Doe", status="Active", is_admin=False)

# Bind directly to props
card = Container(
    # Bind text property
    Text(text=useDynamic("user.name"), style="font-bold"),

    # Bind input value
    Input(value=useDynamic("user.name"), placeholder="Edit name"),

    # Bind button text and disabled state
    Button(
        text=useDynamic("user.status"),
        disabled=useDynamic("user.is_admin"),
        on_click=userState.status.set("Clicked!")
    )
)

Supported Properties

useDynamic and useValue supports binding to the following properties on built-in components:

Component Properties
Text text , innerHTML
Button text , disabled
Input value , placeholder , disabled , readonly , required
Textarea value , placeholder , disabled , readonly , required
Image src , alt
Link href , text
Checkbox checked , disabled , required
RadioButton checked , disabled , required
Select disabled , required
Slider disabled

Boolean attributes like disabled and checked will be toggled based on the truthiness of the state value.

2. Usage in FunctionComponents

You can also use useDynamic() within FunctionComponent templates to create reactive spans.

@FunctionComponent
def UserCard(**props):
    return f'''
    <div {Props.id} {Props.class_name} {Props.style}>
        <h3>Name: {useDynamic("user.name")}</h3>
        <p>Status: {useDynamic("user.status")}</p>
    </div>
    '''

Syntax

useDynamic(state_path: str) -> DynamicBinding

Parameters:

  • state_path : Dot-notation path to state property (e.g., "user.name" , "cart.total" )

Returns:

  • DynamicBinding object that resolves to the current value during render and updates automatically when state changes.

useValue() - Initial Value Access

The useValue() hook allows you to access the initial value of a state property without creating a reactive binding. This is ideal for form inputs where you want to set a default value but allow the user to edit it freely.

Basic Usage

Pass useValue() to component properties to set their initial value from state:

from dars.all import *

userState = State("user", name="John Doe", email="john@example.com")

# Input with initial value from state (editable by user)
Input(value=useValue("user.name"))

# Textarea with initial value
Textarea(value=useValue("user.email"))

Usage in FunctionComponents with Selectors

useValue() supports automatic selector application in FunctionComponents! When you provide a selector (class or ID), it will be automatically applied to the element where the value is used.

from dars.all import *

app = App("Example of hooks")

userState = State("user", name="Jane Doe", email="jane@example.com", display="None")

@FunctionComponent
def UserForm(**props):
    return f'''
    <div {Props.id} {Props.class_name} {Props.style}>
        <input value="{useValue("user.name", ".name-input")}" />
        <input value="{useValue("user.email", "#email-field")}" />
        <span>{useValue("user.age", ".age-display")}</span>
        <span>{useDynamic("user.display")}</span>
    </div>
    '''


@route("/")
def index():
    return Page(
        UserForm(id="user-form"),
        # Extract values using V() helper with the selectors
        Button(
            "Get Name",
            on_click=userState.display.set(
                "Name: " + V(".name-input")  # Extract current value
            )
        ),

        Button(
            "Combine Values",
            on_click=userState.display.set(
                V(".name-input") + " (" + V("#email-field") + ")"
            )
        )
    )

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

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

How it works:

  1. useValue("user.name", ".name-input") sets initial value "Jane Doe" and applies class name-input to the input
  2. User can edit the value freely
  3. V(".name-input") extracts the current value (even if modified by user)
  4. Perfect for forms where you need both initial values and value extraction

Supported selectors:

  • Class selectors ( .foo ) → Added to element's class attribute
  • ID selectors ( #bar ) → Set as element's id attribute
Difference from useDynamic
  • useDynamic("state.prop") : Creates a reactive binding . If the state changes, the input value updates automatically.
  • useValue("state.prop") : Sets the initial value only . If the state changes later, the input value does NOT update. This prevents overwriting user input while they are typing.

Syntax

useValue(state_path: str, selector: str = None) -> ValueMarker

Parameters:

  • state_path : Dot-notation path to state property (e.g., "user.name" )
  • selector : Optional CSS selector (class or ID) to apply to the element

Returns:

  • ValueMarker object that resolves to the initial value during component rendering.

useWatch() - State Monitoring

The useWatch() hook allows you to monitor state changes and execute callbacks (side effects). It supports watching single or multiple state properties and executing one or more callbacks.

Basic Usage

The recommended way to use useWatch is via the app.useWatch() or page.useWatch() methods:

Single State Property

from dars.all import *

cartState = State("cart", count=0, total=0.0)

# Logs to console whenever cart.count changes
app.useWatch("cart.count", log("Cart updated!"))
app.useWatch("cart.total", log("Total changed"))

Multiple State Properties (Array Syntax)

productState = State("product", name="Widget", price=19.99, info="")

# Watch multiple properties - callback executes when ANY of them change
app.useWatch(
    ["product.name", "product.price"],
    productState.info.set("Product: " + V("product.name") + " - $" + V("product.price"))
)

Multiple Callbacks

# Execute multiple callbacks when state changes
app.useWatch(
    "cart.total",
    log("Total changed!"),
    alert("Cart updated")
)

# Combine array syntax with multiple callbacks
app.useWatch(
    ["product.name", "product.price"],
    productState.info.set("Product: " + V("product.name") + " - $" + V("product.price")),
    log("Product info updated")
)

Page-Specific Watchers (page.useWatch)

@route("/cart")
def cart_page():
    page = Page()

    # This watcher only runs on the cart page
    page.useWatch("cart.total", log("Total changed!"))

    page.add(
        Container(
            Text(useDynamic("cart.total"))
        )
    )
    return page

You can also use the classic syntax with add_script :

app.add_script(useWatch("state.prop", log("Changed!")))

Syntax

useWatch(
    state_path: Union[str, List[str]],
    *callbacks: Union[dScript, str, Callable]
) -> Union[dScript, WatchMarker]

Parameters:

  • state_path : State property path(s) to watch. Can be:
    • Single path string (e.g., "user.name" )
    • List of paths (e.g., ["product.name", "product.price"] )
  • *callbacks : One or more callbacks to execute when state changes. Each can be:
    • dScript object (e.g., log("Changed") , alert("Update") )
    • State setter (e.g., productState.info.set(...) )
    • Inline JavaScript string
    • Python callable returning a dScript

Behavior:

  • When using an array of state paths, the callback(s) execute when any of the watched properties change
  • Multiple callbacks execute in the order they are provided
  • Callbacks can access current state values using V() helper

Important: State ID Best Practices

[!IMPORTANT] When using State objects with hooks like useDynamic and useValue , the state ID should NOT match any component ID in your DOM. The state ID is a unique identifier for the state object itself, not a component.

Why This Matters

The reactive system uses watchers to update components when state changes. When you create a State object, the ID you provide is used to register the state in the internal registry, not to identify a specific DOM element.

Examples

X Incorrect - State ID matches component ID:

# DON'T do this
state = State("my-button", count=0, disabled=False)
Button(id="my-button", text=useDynamic("my-button.count"))

In this example, both the state and the button have the ID "my-button" , which can cause confusion and unexpected behavior.

✓ Correct - State has unique ID:

# DO this - give state a descriptive, unique ID
counter_state = State("counter-state", count=0, disabled=False)
Button(id="my-button", text=useDynamic("counter-state.count"))
Button(id="another-button", disabled=useDynamic("counter-state.disabled"))

✓ Also Correct - Multiple components sharing same state:

# One state can control multiple components
ui_state = State("ui", count=0, is_disabled=False, message="Hello")

Container(
    Text(text=useDynamic("ui.message")),
    Button(id="btn-1", disabled=useDynamic("ui.is_disabled")),
    Button(id="btn-2", disabled=useDynamic("ui.is_disabled")),
    Text(text=useDynamic("ui.count"))
)

Key Takeaways

  1. State IDs are for the state object , not for DOM elements
  2. One state can control many components through reactive bindings
  3. Component IDs should be unique across your DOM
  4. State IDs should be descriptive of what they manage (e.g., "user-data" , "cart-state" , "ui-controls" )

4. Forms & Validation

Form Collection System

Pythonic form data collection and submission without raw JavaScript!

FormData & collect_form()

The FormData class and collect_form() helper provide a declarative way to collect form data using V() expressions.

Basic Usage
from dars.all import *

# Collect form data with kwargs syntax
form_data = collect_form(
    name=V("#name-input"),
    email=V("#email-input"),
    age=V("#age-input").int(),
    is_premium=V("#premium-checkbox")
)

# Show in alert
Button("Submit", on_click=form_data.alert())

# Log to console
Button("Log", on_click=form_data.log())

# Save to state
Button("Save", on_click=form_data.to_state(state.data))
Advanced Features

Nested Dictionaries & Lists:

form_data = collect_form(
    name=V("#name"),
    email=V("#email"),

    # Nested validation results
    validation={
        "email_valid": V("#email").includes("@"),
        "age_ok": (V("#age").int() >= 18).and_(
                   V("#age").int() <= 120)
    },

    # Conditional values
    discount=(V("#premium").bool()).then("10%", "0%"),

    # Timestamp
    submitted_at=getDateTime()
)

Alternative Syntaxes:

# Using tuples
form_data = collect_form(
    ("name", V("#name")),
    ("email", V("#email"))
)

# Using dict
form_data = collect_form({
    "name": V("#name"),
    "email": V("#email")
})
FormData Methods

.alert(title) - Show form data in alert dialog:

Button("Show Data", on_click=form_data.alert("Form Data"))

.log(message) - Log form data to console:

Button("Log Data", on_click=form_data.log("Form submitted"))

.to_state(property) - Save form data to state:

Button("Save", on_click=form_data.to_state(state.form_data))

.submit(url, state_property, on_success, on_error) - Submit to backend:

# Simple submit
Button("Submit", on_click=form_data.submit("http://localhost:3000/submit"))

# With state and callbacks
Button("Submit", on_click=form_data.submit(
    url="http://localhost:3000/submit",
    state_property=state.response,
    on_success=alert("Success!"),
    on_error=alert("Error!")
))

.submit_and_alert(state_property, title) - Submit with alert:

Button("Submit", on_click=form_data.submit_and_alert(
    state.data,
    "Form Submitted!"
))
Backend Integration Example
from dars.all import *

app = App("Form with Backend")
form = State("form", response="")

# Collect form data
form_data = collect_form(
    name=V("#name"),
    email=V("#email"),
    age=V("#age").int(),
    submitted_at=getDateTime()
)

@route("/")
def index():
    return Page(
        Container(
            Input(id="name", placeholder="Name"),
            Input(id="email", placeholder="Email"),
            Input(id="age", input_type="number", placeholder="Age"),

            # Submit to backend
            Button(
                "Submit to Backend",
                on_click=form_data.submit(
                    url="http://localhost:3000/submit",
                    state_property=form.response,
                    on_success=alert("Form submitted successfully!")
                )
            ),

            # Display backend response
            Container(
                Text("Backend Response:", style="font-bold"),
                Text(text=useDynamic("form.response"))
            )
        )
    )

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

FormValidator — Client-Side Validation

Declarative form validation with dual client/server enforcement.

Rules

Constructor Description
required() Field must be non-empty
min_length(n) Minimum character count
max_length(n) Maximum character count
email() Must be a valid email address
pattern(regex) Must match regex
min_value(n) Numeric minimum
max_value(n) Numeric maximum
custom(fn) Python callable (value) -> Optional[str]

validated_submit

Validates all rules client-side first. Only fires the network request if every rule passes. Error messages appear in #{field}-error elements.

task_form = collect_form(title=V("#title"))

validator = FormValidator({
    "title": [required(), min_length(3), max_length(100)],
})

submit_action = validator.validated_submit(
    url="/api/tasks",
    form_data=task_form,
    on_success=runSequence(clearInput("title"), fetch_trigger),
    on_error=setText("submit-error", "Error submitting. Try again."),
)

# In your Page:
Input(id="title", placeholder="Task title…"),
Text("", id="title-error", style="text-red-500 text-sm"),
Button("Add Task", on_click=submit_action),

Important: The input id must match the field name in FormValidator so the selector #title resolves correctly.

Server-Side Validation

errors = validator.validate_server({"title": "Hi"})
# → {"title": ["Must be at least 3 characters."]}

errors = validator.validate_server({"title": "Hello World"})
# → {}  (all pass)

5. Network & Async Operations

useFetch() — Declarative Data Fetching

useFetch is the primary hook for fetching data from APIs and binding the response to reactive VRefs. It returns a 4-tuple of (trigger, loading_vref, data_vref, error_vref) — all pure Python objects.

Basic Usage

from dars.all import *

trigger, loading, data, error = useFetch("/api/users")

page = Page(
    Show(loading, Spinner()),
    Show(error,   Text("Error loading data", style="text-red-500")),
    Button("Reload", on_click=trigger),
)
page.add_script(trigger)  # auto-run on page load

With Callbacks

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

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

POST with Body

trigger, loading, data, error = useFetch(
    "/api/search",
    method="POST",
    body={"query": "dars"},
    headers={"Content-Type": "application/json"},
)
Signature
useFetch(
    url: str,
    method: str = "GET",
    body: Any = None,
    headers: dict = None,
    on_success: dScript = None,
    on_error: dScript = None,
) -> Tuple[dScript, VRefValue, VRefValue, VRefValue]

Returns: (trigger_script, loading_vref, data_vref, error_vref)

  • trigger_script dScript that initiates the fetch. Use as on_click handler or in runSequence , or pass to page.add_script() to auto-run on load.
  • loading_vref VRefValue that is True while the request is in flight.
  • data_vref VRefValue that holds the parsed response on success.
  • error_vref VRefValue that holds the error message on failure.

updateVRefFromResponse() — Store Fetch Response

Stores the API response from a useFetch on_success context into a VRef selector. The network_request DAP op passes the parsed response as ctx.response .

updateVRefFromResponse(selector: str, key: str = "response") -> dScript
on_success=runSequence(
    updateVRef(".loading", False),
    updateVRefFromResponse(".tasks-data"),  # stores ctx.response → VRef
)

Nested JSON Path Extraction (Dot-Notation)

You can use dot-notation in the key parameter to drill down into nested JSON response structures (e.g. response.user.username , response.data.items ). This makes Dars completely compatible with any backend or third-party JSON API, extracting just the data you need without manual transformation.

on_success=runSequence(
    updateVRefFromResponse(".user-name", key="response.user.username"),
    updateVRefFromResponse(".user-role", key="response.user.role"),
)

runSequence() — Chain Multiple Actions

Execute multiple dScript / RawJS actions in sequence. Accepts any mix of hooks, VRef updates, fetch triggers, and DAP actions.

from dars.all import *

Button("Submit",
    on_click=runSequence(
        updateVRef(".loading", True),
        fetch_trigger,
        clearInput("my-input"),
    )
)

6. Appendix

Best Practices

Do:

  • Use useDynamic for simple text/value updates.
  • Use useWatch for side effects like logging, analytics, or complex logic.
  • Use useValue with selectors for form inputs that need value extraction.
  • Use consistent state naming (e.g., "user" , "cart" ).
  • Always use .int() or .float() before arithmetic operations with V() .

Don't:

  • Use with non-existent state paths.
  • Nest state paths more than 2 levels deep (currently supports stateName.property ).
  • Use arithmetic operators without numeric transformations.

Operations in Dars (DAP)

Dars Action Protocol (DAP) is the secure command system that powers all client-side interactivity. Every event handler, animation, and state mutation compiles down to structured DAP payloads — never eval() or new Function() .

All DAP Operations

Operation Description Python Helper
navigate Navigate to path goTo(path)
navigate_new Open in new tab goToNew(path)
reload Reload page reload()
history_back Go back historyBack()
history_forward Go forward historyForward()

State Management

Operation Description Python Helper
change Change state value state.count.set(n)
Operation Description Python Helper
modal_show Show modal showModal(id)
modal_hide Hide modal hideModal(id)

DOM Visibility

Operation Description Python Helper
dom_show Show element show(id)
dom_hide Hide element hide(id)
dom_toggle Toggle visibility toggle(id)

DOM Content

Operation Description Python Helper
dom_set_text Set text content setText(id, text)
dom_set_html Set inner HTML (sanitized) setHTML(id, html)
dom_set_style Set inline style setStyle(id, styles)
dom_set_value Set form value setValue(id, val)
dom_set_attr Set attribute setAttr(id, name, val)
dom_reflow Force browser reflow reflow(id)
dom_animate Animate element animate(id, keyframes)

CSS Classes

Operation Description Python Helper
class_add Add CSS class addClass(id, cls)
class_remove Remove CSS class removeClass(id, cls)
class_toggle Toggle CSS class toggleClass(id, cls)

Scroll

Operation Description Python Helper
scroll_to Scroll to position scrollTo(x, y)
scroll_top Scroll to top scrollTop()
scroll_bottom Scroll to bottom scrollBottom()
scroll_to_element Scroll element into view scrollToElement(id)

Forms

Operation Description Python Helper
form_submit Submit form formSubmit(id)
form_reset Reset form formReset(id)
input_clear Clear input clearInput(id)
input_set Set input value inputSet(id, value)

Clipboard

Operation Description Python Helper
clipboard_write Write to clipboard copyToClipboard(text)
clipboard_copy_element Copy element text copyElementText(id)

Control Flow

Operation Description Python Helper
sequence Run actions in sequence sequence(*actions)
delay Delay then run action delay(ms, action)
conditional Conditionally run action condition(expr, then, else)

VRef

Operation Description Python Helper
vref_update Update VRef value updateVRef(sel, val)
vref_get Get VRef value

Dynamic Components

Operation Description Python Helper
comp_create Create component dynamically createComp(data, root)
comp_delete Delete component deleteComp(id)
comp_update Update component props updateComp(id, props)

Network

Operation Description Python Helper
fetch HTTP request fetch(url, ...)
call_server Call server action call_server(name, **kwargs)

Dialogs

Operation Description Python Helper
alert Show alert alert(msg)
confirm Show confirmation confirm(msg, on_ok, on_cancel)
log Console log log(msg)

Security Model

  1. Compile-time : Python code is compiled to structured DAP {op, args} payloads
  2. No eval : The browser runtime's _dispatch() function only executes predefined operations
  3. No new Function : Dynamic scripts use secure IIFE injection, not new Function()
  4. DOMPurify : All dom_set_html operations sanitize input before DOM injection

Backend & Fullstack API

Dars provides a complete fullstack toolkit: HTTP utilities, a declarative fetch hook, form validation, file uploads, a JSON store, security middleware, and environment management — all in pure Python.

Table of Contents


SSR Backend Setup

Dars SSR projects use SSRApp to wire together FastAPI, CORS, security headers, and file uploads in one place.

Project Structure

my-app/
├── main.py                  # Dars frontend (routes, components)
├── backend/
│   ├── api.py               # FastAPI entry point
│   └── apiConfig.py         # Environment config
├── dars.config.json
└── .env                     # Environment variables (auto-loaded)

backend/api.py

from dars.backend.ssr import SSRApp
from backend.apiConfig import DarsEnv
from main import app as dars_app

ssr = SSRApp(dars_app, prefix="/api/ssr", title="My App - Backend")

urls = DarsEnv.get_urls()
ssr.use_cors(
    origins=[urls["frontend"], "http://127.0.0.1:4000"],
    credentials=True,
)
ssr.use_security_headers()
ssr.use_upload(
    upload_dir="uploads",
    allowed_types=["image/png", "image/jpeg", "application/pdf"],
    max_size_bytes=10 * 1024 * 1024,
    path="/api/upload",
)

app = ssr.fastapi_app

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

# Custom API routes
from fastapi import Request
from fastapi.responses import JSONResponse
from dars.backend.store import JsonStore

_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)

if __name__ == "__main__":
    import uvicorn
    print("\n" + "=" * 60)
    print("Dars Fullstack Backend")
    print("=" * 60)
    if DarsEnv.is_dev():
        print(f"  Backend:  {urls['backend']}")
        print(f"  Frontend: {urls['frontend']}  (run 'dars dev' separately)")
        print(f"  API Docs: {urls['backend']}/docs")
        port, host = 3000, "127.0.0.1"
    else:
        frontend_dir = DarsEnv.get_frontend_dist_dir()
        print(f"  App:      http://0.0.0.0:8000  (frontend + API, same origin)")
        print(f"  Frontend: {frontend_dir}")
        port, host = 8000, "0.0.0.0"
    print("=" * 60 + "\n")
    uvicorn.run(app, host=host, port=port)

backend/apiConfig.py

import os

class DarsEnv:
    MODE = os.environ.get("DARS_MODE", "development")

    @staticmethod
    def is_dev():
        return DarsEnv.MODE == "development"

    @staticmethod
    def get_urls():
        if DarsEnv.is_dev():
            return {"backend": "http://localhost:3000", "frontend": "http://localhost:4000"}
        return {"backend": "/", "frontend": "/"}

Running

# Run frontend and backend together in development
# (backendEntry must be configured in dars.config.json)
dars dev

Note: dars dev --backend is deprecated in v1.9.14. Use dars dev to start the fullstack development workflow.


useFetch — Declarative Data Fetching

useFetch creates a fetch trigger and three reactive VRefs (loading, data, error) wired to a network_request DAP action. No JavaScript needed.

Signature

useFetch(
    url: str,
    method: str = "GET",
    body: Any = None,
    headers: dict = None,
    on_success: dScript = None,
    on_error: dScript = None,
) -> Tuple[dScript, VRefValue, VRefValue, VRefValue]

Returns (trigger_script, loading_vref, data_vref, error_vref) .

Basic Usage

from dars.all import *

trigger, loading, data_vref, error = useFetch("/api/users")

page = Page(
    Show(loading, Spinner()),
    Show(error,   Text("Error loading data", style="text-red-500")),
    Text(data_vref),
    Button("Reload", on_click=trigger),
)
page.add_script(trigger)  # auto-run on load

With Callbacks

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

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

POST with Body

trigger, loading, data, error = useFetch(
    "/api/search",
    method="POST",
    body={"query": "dars"},
    headers={"Content-Type": "application/json"},
)

updateVRefFromResponse

Stores the API response from a useFetch on_success context into a VRef selector. The network_request DAP op passes the parsed response as ctx.response .

updateVRefFromResponse(selector: str, key: str = "response") -> dScript
on_success=runSequence(
    updateVRef(".loading", False),
    updateVRefFromResponse(".tasks-data"),
)

Nested JSON Path Extraction (Dot-Notation)

You can use dot-notation in the key parameter to drill down into nested JSON response structures (e.g. response.user.username , response.data.items ). This makes Dars completely compatible with any backend or third-party JSON API, extracting just the data you need without manual transformation.

on_success=runSequence(
    updateVRefFromResponse(".user-name", key="response.user.username"),
    updateVRefFromResponse(".user-role", key="response.user.role"),
)

FormValidator — Client-Side Validation

Declarative validation with dual client/server enforcement. Rules are declared once in Python.

Rules

Constructor Description
required() Field must be non-empty
min_length(n) Minimum character count
max_length(n) Maximum character count
email() Must be a valid email address
pattern(regex) Must match regex
min_value(n) Numeric minimum
max_value(n) Numeric maximum
custom(fn) Python callable (value) -> Optional[str]

validated_submit

Validates all rules client-side first. Only fires the network request if every rule passes. Error messages appear in #{field}-error elements.

from dars.all import *

task_form = collect_form(title=V("#title"))

validator = FormValidator({
    "title": [required(), min_length(3), max_length(100)],
})

submit_action = validator.validated_submit(
    url="/api/tasks",
    form_data=task_form,
    on_success=runSequence(clearInput("title"), fetch_trigger),
    on_error=setText("submit-error", "Error submitting. Try again."),
)

# In your Page:
Input(id="title", placeholder="Task title…"),
Text("", id="title-error", style="text-red-500 text-sm"),
Button("Add Task", on_click=submit_action),

Important: The input id must match the field name in FormValidator so the selector #title resolves correctly.

Server-Side Validation

errors = validator.validate_server({"title": "Hi"})
# → {"title": ["Must be at least 3 characters."]}

errors = validator.validate_server({"title": "Hello World"})
# → {}  (all pass)

Get Rules as JSON

rules_json = validator.get_rules_json()
# → '{"title": [{"type": "required"}, {"type": "min_length", "n": 3}]}'

JsonStore — File-Backed Storage

Thread-safe, atomic-write JSON persistence. Ideal for prototyping and small backends.

from dars.all import *

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

# Read
tasks = store.get("tasks", [])

# Write (atomic)
store.set("tasks", tasks + [{"id": 1, "title": "New task"}])

# Delete key
store.delete("tasks")

# Get all
all_data = store.all()

# Clear
store.clear()
  • Writes to a .tmp file then atomically replaces the target via os.replace()
  • Per-instance threading.Lock on all mutating operations
  • Raises ValueError on malformed JSON with a descriptive message

UploadPipeline — File Uploads

Server-side file upload handler with MIME type validation, size limits, and filename sanitisation.

from dars.all import *

pipeline = UploadPipeline(
    upload_dir="uploads",
    allowed_types=["image/png", "image/jpeg", "image/gif", "application/pdf"],
    max_size_bytes=10 * 1024 * 1024,  # 10 MB
)
pipeline.create_endpoint(app, path="/api/upload")
Status Condition
200 Upload successful — returns {"url": "/uploads/filename.png"}
413 File exceeds max_size_bytes
415 MIME type not in allowed_types
500 Disk write failure

Custom Rename

import uuid

pipeline = UploadPipeline(
    upload_dir="uploads",
    rename_fn=lambda name: f"{uuid.uuid4().hex}_{name}",
)

Filename Sanitisation

UploadPipeline.sanitize_filename(filename) removes ../ , ./ , and any character outside [a-zA-Z0-9._-] .

FileUpload 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

Injects HTTP security headers into every response. Headers are only added when not already present, so application code can override any individual header.

from dars.all import *

# Via SSRApp (recommended)
ssr.use_security_headers()

# Or manually
from dars.backend.middleware import SecurityHeadersMiddleware
app.add_middleware(SecurityHeadersMiddleware, csp="default-src 'self'", hsts=True)

Default headers injected:

Header Value
X-Content-Type-Options nosniff
X-Frame-Options DENY
X-XSS-Protection 1; mode=block
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy geolocation=(), microphone=(), camera=()

Optional: - csp="..." → adds Content-Security-Policy - hsts=True → adds Strict-Transport-Security: max-age=31536000; includeSubDomains


DarsEnv — Environment Variables

DarsEnv loads .env files and provides typed access to environment variables.

.env File

API_KEY=my-secret-key
DATABASE_URL=sqlite:///./app.db
DEBUG=true

Usage

from dars.env import DarsEnv

# Load .env (called automatically by load_config)
DarsEnv.load()
DarsEnv.load(path="config/.env")  # custom path

# Read
api_key = DarsEnv.get("API_KEY")           # None if missing
api_key = DarsEnv.get("API_KEY", "default")

# Require (raises KeyError if missing)
secret = DarsEnv.require("SECRET_KEY")

# Dev/prod mode
DarsEnv.set_dev_mode(True)
if DarsEnv.dev:
    print("Running in development mode")

Rules: - Does not overwrite existing os.environ keys - Strips surrounding single or double quotes from values - Skips blank lines and # comments - Splits on the first = only (values may contain = ) - Called automatically by load_config() before reading dars.config.json


HTTP Client Utilities

Lower-level HTTP helpers that return dScript objects for use in event handlers.

from dars.backend.http import fetch, get, post, put, delete, patch

# GET
get_users = get(id="userData", url="/api/users")

# POST
create_user = post(
    id="createResult",
    url="/api/users",
    body={"name": "Alice"},
    callback=alert("User created!"),
    on_error=alert("Error!"),
)

Interceptors

from dars.backend.http import add_request_interceptor, use_auth_interceptor

# Add auth token to every request
use_auth_interceptor(token_key="dars_auth_token")

# Custom interceptor
add_request_interceptor(lambda cfg: {**cfg, "headers": {**cfg.get("headers", {}), "X-App": "1"}})

useData — Access Response Data

from dars.backend.data import useData

# Dot-notation access to fetched data
name_state.text.set(useData("userData").name)
city_state.text.set(useData("userData").address.city)

Component Management

Create, update, and delete components dynamically at runtime.

from dars.all import *

new_item = Text("Hello!", id="new-item")

Button("Add",    on_click=createComp(new_item, root="container-id", position="append"))
Button("Remove", on_click=deleteComp("new-item"))
Button("Update", on_click=updateComp("new-item", text="Updated!"))

Full Fullstack Example

A complete task manager using useFetch , Each , FormValidator , JsonStore , and SSRApp .

Frontend ( pages/did.py )

from dars.all import *

@route("/did")
def did():
    loading_sel = ".tasks-loading"
    error_sel   = ".tasks-error"
    tasks_sel   = ".tasks-data"

    is_loading = setVRef(True,  loading_sel)
    has_error  = setVRef(False, error_sel)
    _tasks     = setVRef([],    tasks_sel)

    on_fetch_success = runSequence(
        updateVRef(loading_sel, False),
        updateVRef(error_sel,   False),
        updateVRefFromResponse(tasks_sel),
    )
    on_fetch_error = runSequence(
        updateVRef(loading_sel, False),
        updateVRef(error_sel,   True),
    )

    fetch_trigger, _lv, _dv, _ev = useFetch(
        "/api/tasks",
        on_success=on_fetch_success,
        on_error=on_fetch_error,
    )

    task_form = collect_form(title=V("#title"))
    validator = FormValidator({"title": [required(), min_length(3), max_length(100)]})

    submit_action = validator.validated_submit(
        url="/api/tasks",
        form_data=task_form,
        on_success=runSequence(clearInput("title"), fetch_trigger),
        on_error=setText("submit-error", "Error submitting. Try again."),
    )

    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 mb-1 bg-white shadow-sm",
        )

    page = Page(
        Container(
            Head("Task Manager"),
            Text("Task Manager", style="text-3xl font-bold mb-1 text-indigo-600"),

            Show(is_loading, Container(Spinner(), Text("Loading…"), style="flex gap-2 mb-4")),
            Show(has_error,  Container(Text("Backend not running?", style="text-red-600"),
                                       style="bg-red-50 border rounded p-3 mb-4")),

            Each(items=_tasks, render=task_item, class_name="space-y-1 mb-6 min-h-[40px]"),

            Container(
                Text("Add a task", style="font-semibold mb-2"),
                Input(id="title", placeholder="Task title (min 3 chars)…",
                      class_name="border rounded px-3 py-2 w-full mb-1"),
                Text("", id="title-error",  style="text-red-500 text-sm mb-1"),
                Text("", id="submit-error", style="text-red-500 text-sm mb-2"),
                Button("Add Task", on_click=submit_action,
                       style="bg-indigo-600 text-white px-4 py-2 rounded"),
                style="bg-white border rounded-xl p-4 shadow-sm mb-4",
            ),
            Button("↻ Refresh", on_click=fetch_trigger,
                   style="text-sm text-indigo-500 underline"),
            style="max-w-xl mx-auto p-8 font-sans",
        )
    )
    page.add_script(fetch_trigger)
    return page

Backend ( backend/api.py )

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

ssr = SSRApp(dars_app, prefix="/api/ssr")
urls = DarsEnv.get_urls()
ssr.use_cors(origins=[urls["frontend"]], credentials=True)
ssr.use_security_headers()

app = ssr.fastapi_app

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

_store = JsonStore(
    path=os.path.join(os.path.dirname(__file__), "..", "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)

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)

How It All Connects

  1. Page loads → fetch_trigger fires → network_request DAP op hits /api/tasks
  2. Backend reads from JsonStore → returns {"tasks": [...]}
  3. on_success runs → updateVRefFromResponse(".tasks-data") stores response in VRef
  4. dom_each_render detects VRef change → substitutes __item_title__ , __item_id__ placeholders → list renders
  5. User types in #title input → clicks "Add Task"
  6. FormValidator checks required() + min_length(3) client-side
  7. If valid → network_request POSTs to /api/tasks → backend appends to JsonStore
  8. on_success clearInput("title") + fetch_trigger → list refreshes

API Reference

useFetch(url, method, body, headers, on_success, on_error)

Returns (trigger, loading_vref, data_vref, error_vref) .

updateVRefFromResponse(selector, key="response")

Stores ctx[key] from fetch context into a VRef.

FormValidator(fields)

  • .validated_submit(url, form_data, on_success, on_error) — validate then submit
  • .validate_server(data) dict of errors
  • .get_rules_json() → JSON string

JsonStore(path, default)

  • .get(key, default) , .set(key, value) , .delete(key) , .all() , .clear()

UploadPipeline(upload_dir, allowed_types, max_size_bytes, rename_fn)

  • .create_endpoint(app, path) — registers FastAPI POST endpoint
  • .sanitize_filename(filename) — static method

SecurityHeadersMiddleware(app, csp, hsts)

Starlette middleware. Use via ssr.use_security_headers() or app.add_middleware(...) .

DarsEnv

  • .load(path=".env") , .get(key, default) , .require(key) , .set_dev_mode(bool)

Database & Models

Dars ships with a built-in declarative ORM for SQLite, featuring auto-generated CRUD API endpoints.

Defining Models

from dars.backend.models import DarsModel, TextField, IntegerField, BooleanField, FloatField, DateTimeField, JSONField, ForeignKey
from dars.backend.database import Database

class Product(DarsModel):
    __tablename__ = "products"
    name = TextField(nullable=False)
    price = IntegerField(default=0)
    in_stock = BooleanField(default=True)

class Order(DarsModel):
    __tablename__ = "orders"
    product = ForeignKey(Product)
    quantity = IntegerField(default=1)

Available Field Types

Field SQL Type Description
TextField() TEXT String values
IntegerField() INTEGER Integer values
FloatField() REAL Float values
BooleanField() INTEGER (0/1) Boolean values
DateTimeField() TEXT (ISO 8601) Datetime values ( auto_now=True for timestamps)
JSONField() TEXT (JSON) JSON-serializable data
ForeignKey(model) INTEGER Foreign key reference

Database Connection

from dars.backend.database import Database

db = Database("app.db")    # File-based
db = Database(":memory:")  # In-memory (default)

db.register(Product, Order)
db.create_all()

Features: - Thread-safe via threading.local connections - WAL journal mode for concurrent reads - Foreign key enforcement - Migration tracking via _dars_schema_version table - Raw SQL: db.execute() , db.fetch_all() , db.fetch_one()

CRUD Operations

# Create
product = Product(name="Widget", price=99, in_stock=True)
product.save()

# Read by ID
product = Product.objects.get(1)

# List all
all_products = Product.objects.all()

# Filter
cheap = Product.objects.filter(price=0)

# Count
count = Product.objects.count()

# Create shorthand
new_product = Product.objects.create(name="Gadget", price=49)

# Update
product.price = 79
product.save()

# Delete
Product.objects.delete(1)
# or
product.delete()

Auto-Generated CRUD API

Wire up REST endpoints for all registered models in one call:

from dars.backend.models import register_model_api

register_model_api(app, db, prefix="/api/models")

This generates for each model (e.g. Product → table products ):

Method Endpoint Description
GET /api/models/products List all
GET /api/models/products/{id} Get by id
POST /api/models/products Create
PUT /api/models/products/{id} Update
DELETE /api/models/products/{id} Delete

All endpoints accept and return JSON with automatic Pydantic validation.


Server Actions

Server Actions let you call Python backend functions directly from client-side events with automatic parameter validation, serialization, and error handling.

Defining a Server Action

Use the @server_action decorator:

from dars.backend.actions import server_action, call_server

@server_action(csrf_protected=False)
def greet(name: str, count: int = 1) -> list:
    """Returns a list of personalized greetings."""
    return [f"Hello {name}! x{i}" for i in range(count)]

@server_action(name="custom_name", auth_required=True)
def admin_action(x: int) -> int:
    return x * 2

Parameters are automatically validated via Pydantic models built from type annotations.

Calling from Components

Use call_server() in event handlers:

Button("Greet", on_click=call_server("greet", name="World", count=3))

# With success/error callbacks:
Button("Save", on_click=call_server(
    "save_user",
    name="Alice",
    on_success=log("Saved!"),
    on_error=log("Failed!"),
))

Registration & Autodiscovery

Actions are auto-registered when you start the backend:

app.start_backend()  # calls register_actions_on_app(fastapi_app)

Or use autodiscovery for modular projects:

from dars.backend.actions import discover_actions

discover_actions("backend.api")
discover_actions("app.actions")

Protected Actions

@server_action(auth_required=True, roles=["admin"])
def delete_user(user_id: int) -> dict:
    # Only authenticated users with "admin" role can call this
    return {"deleted": user_id}

Features

  • Type-annotated parameters validated via Pydantic
  • Sync and async support
  • Automatic JSON serialization of results
  • Structured error responses (400/500)
  • CSRF protection for mutating actions
  • call_server() returns a DAP action compatible with the Dars runtime
  • Actions are exposed as POST /api/actions/{action_name} endpoints

Middleware System

Dars provides a complete middleware pipeline for FastAPI/Starlette applications:

Available Middleware

Middleware Description
AuthMiddleware JWT Bearer/cookie validation with CSRF protection
SecurityHeadersMiddleware CSP, HSTS, X-Frame-Options, and more
CORSMiddleware Configurable cross-origin resource sharing
RateLimitMiddleware Per-IP / per-user sliding-window rate limiting
LoggingMiddleware Structured request/response logging
CompressionMiddleware Gzip response compression

Quick Setup

from dars.backend.middleware import register_default_middlewares

register_default_middlewares(app)

Custom Middleware

from dars.backend.middleware import DarsMiddleware
from starlette.requests import Request
from starlette.responses import Response

class MyMiddleware(DarsMiddleware):
    async def before_request(self, request: Request):
        # Return a Response to short-circuit, or None to continue
        pass

    async def after_response(self, request: Request, response: Response) -> Response:
        # Mutate response
        return response

Events in Dars

This is the documentation for the events in Dars.

Event Calling System

Custom components in Dars can have events associated with them. You can set an event on a custom component using the set_event method.

self.set_event(EventTypes.CLICK, log('click'))

Available Event Types

To use the event types, you need to import them from dars.core.events :

from dars.core.events import EventTypes

Here are the different event types available:

  • Mouse Events:

    • CLICK = "click"
    • DOUBLE_CLICK = "dblclick"
    • MOUSE_DOWN = "mousedown"
    • MOUSE_UP = "mouseup"
    • MOUSE_ENTER = "mouseenter"
    • MOUSE_LEAVE = "mouseleave"
    • MOUSE_MOVE = "mousemove"
  • Keyboard Events:

    • KEY_DOWN = "keydown"
    • KEY_UP = "keyup"
    • KEY_PRESS = "keypress"
  • Form Events:

    • CHANGE = "change"
    • INPUT = "input"
    • SUBMIT = "submit"
    • FOCUS = "focus"
    • BLUR = "blur"
  • Load Events:

    • LOAD = "load"
    • ERROR = "error"
    • RESIZE = "resize"

New in v1.2.2: Event arrays and dynamic handlers

  • Any on_* attribute can now accept:
    • A single script (InlineScript, FileScript, dScript) or plain JS string
    • An array mixing any of the above (executed sequentially)

Example using Mod.set :

Mod.set("btn1", on_click=[st1.state(0), log('clicked')])

Runtime behavior:

  • Only one dynamic listener per event is active at a time; subsequent Mod.set replaces the previous one.
  • Dynamic handlers run in capture phase and stop propagation for the same event.
  • Returning to the default state (index 0) removes any dynamic listeners from that element and restores its initial DOM.

Backend HTTP Integration

Dars provides HTTP utilities that can be used directly in event handlers:

from dars.all import *
from dars.backend import get, post, useData

# GET request on button click
fetch_btn = Button(
    "Fetch Data",
    on_click=get(
        id="apiData",
        url="https://api.example.com/data",
        callback=status_state.text.set("✅ Loaded!")
    )
)

# POST request with data binding
submit_btn = Button(
    "Submit",
    on_click=post(
        id="submitResult",
        url="https://api.example.com/submit",
        body={"name": "John", "email": "john@example.com"},
        callback=result_state.text.set(useData('submitResult').message)
    )
)

# Chain HTTP request with state updates
button.on_click = [
    status_state.text.set("Loading..."),
    get(
        id="userData",
        url="https://api.example.com/user/1",
        callback=(
            name_state.text.set(useData('userData').name)
            .then(status_state.text.set("Done!"))
        )
    )
]

Keyboard Events in Dars

Dars provides a powerful and intuitive system for handling keyboard events in your applications. This guide covers everything from basic key detection to advanced global shortcuts.


Basic Keyboard Events

All Dars components support the on_key_press event handler for keyboard interactions.

Simple Example

from dars.all import *

Input(
    id="search",
    on_key_press=log("Key pressed!")
)

Note: Use on_key_press as the universal keyboard event. The older on_key_down and on_key_up events have been deprecated in favor of this simpler approach.


KeyCode Constants

The KeyCode class provides constants for all keyboard keys, making your code more readable and maintainable.

Available Keys

from dars.all import *

# Navigation keys
KeyCode.ENTER
KeyCode.TAB
KeyCode.ESCAPE  # or KeyCode.ESC
KeyCode.BACKSPACE
KeyCode.DELETE

# Arrow keys
KeyCode.UP       # or KeyCode.ARROWUP
KeyCode.DOWN     # or KeyCode.ARROWDOWN
KeyCode.LEFT     # or KeyCode.ARROWLEFT
KeyCode.RIGHT    # or KeyCode.ARROWRIGHT

# Letters (a-z)
KeyCode.A
KeyCode.B
# ... through ...
KeyCode.Z

# Numbers
KeyCode.ZERO  # or KeyCode.0
KeyCode.ONE   # or KeyCode.1
# ... through ...
KeyCode.NINE  # or KeyCode.9

# Function keys
KeyCode.F1
KeyCode.F2
# ... through ...
KeyCode.F12

# Special characters
KeyCode.SPACE
KeyCode.PLUS
KeyCode.MINUS
KeyCode.SLASH
KeyCode.COMMA
KeyCode.PERIOD

Dynamic Key Access

# Get key code by name
key = KeyCode.key('enter')  # Returns 'Enter'
key = KeyCode.key('A')      # Returns 'a'

The onKey() Helper

The onKey() function is the recommended way to handle specific keyboard keys with optional modifier keys.

Basic Usage

from dars.all import *

# Simple key detection
Input(
    on_key_press=onKey(KeyCode.ENTER, log("Enter pressed!"))
)

With Modifiers

# Ctrl modifier
Container(
    on_key_press=onKey(KeyCode.S, alert("Saving..."), ctrl=True)
)

# Multiple modifiers
Container(
    on_key_press=onKey(KeyCode.Z, log("Redo"), ctrl=True, shift=True)
)

Available Modifiers

  • ctrl - Ctrl key (Command on Mac)
  • shift - Shift key
  • alt - Alt key
  • meta - Meta/Command key

Complete Example

from dars.all import *

app = App("onKey Example")

formState = State("form", message="")

@route("/")
def index():
    return Page(
        Input(
            id="input",
            placeholder="Press Enter to submit",
            on_key_press=onKey(
                KeyCode.ENTER,
                formState.message.set("Submitted!"),
                ctrl=False  # Just Enter, no modifier needed
            )
        ),
        Text(useDynamic("form.message"))
    )

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

The switch() Function

Use switch() to handle multiple different keys in a single event handler.

Basic Usage

from dars.all import *

Input(
    on_key_press=switch({
        KeyCode.ENTER: log("Enter pressed"),
        KeyCode.ESCAPE: alert("Escape pressed"),
    })
)

With Multiple Actions

formState = State("form", username="", password="")

Input(
    on_key_press=switch({
        KeyCode.ENTER: [
            formState.message.set("Submitted!"),
            alert("Form submitted!")
        ],
        KeyCode.ESCAPE: [
            clearInput("username"),
            clearInput("password"),
            formState.message.set("Cleared!")
        ]
    })
)

Complete Example

from dars.all import *

app = App("KeyCode Clean Example")

# State
formState = State("form", 
    username="", 
    password="", 
    message="Use keyboard shortcuts!"
)

@FunctionComponent
def LoginForm(**props):
    return f'''
    <div {Props.id} {Props.class_name} {Props.style}>
        <h2>Login Form with Keyboard Shortcuts</h2>
        <p style="color: #666;">{useDynamic("form.message")}</p>

        <input 
            type="text" 
            id="username-input"
            placeholder="Username"
            style="display: block; margin: 10px 0; padding: 8px; width: 300px;"
        />

        <input 
            type="password" 
            id="password-input"
            placeholder="Password"
            style="display: block; margin: 10px 0; padding: 8px; width: 300px;"
        />

        <div style="margin-top: 30px; padding: 15px; background: #f5f5f5; border-radius: 4px;">
            <h3 style="margin-top: 0;">Global Keyboard Shortcuts:</h3>
            <ul style="margin: 0; padding-left: 20px;">
                <li><strong>Ctrl+Enter</strong> - Submit form (shows alert)</li>
                <li><strong>Ctrl+F</strong> - Clear form</li>
                <li><strong>Ctrl+S</strong> - Save document</li>
            </ul>
            <p style="margin-top: 10px; font-size: 0.9em; color: #666;">
                Note: These are GLOBAL shortcuts that work anywhere on the page without blocking normal typing.
            </p>
        </div>
    </div>
    '''

@route("/")
def index():
    return Page(
        LoginForm(id="login-form"),
        Input(value="", on_key_up=onKey("R", action=log("Logged"))),
        # Buttons using State.set() and utils_ds functions
        Button(
            "Submit",
            on_click=[
                formState.message.set("Form submitted via button!"),
                alert("Form submitted!")
            ]
        ),

        Button(
            "Clear", 
            on_click=[
                formState.username.set(""),
                formState.password.set(""),
                formState.message.set("Form cleared via button!"),
                clearInput("username-input"),
                clearInput("password-input")
            ]
        ),

        Button(
            "Show Username",
            on_click=alert(V("#username-input"))
        ),

        Button(
            "Log Message",
            on_click=log(V("form.message"))
        ),
    )

addGlobalKeys(app, {
    (KeyCode.ENTER, 'ctrl'): [
        formState.message.set("Form submitted with Ctrl+Enter!"),
        alert("Form submitted!")
    ],
    (KeyCode.F, 'ctrl'): [
        formState.username.set(""),
        formState.password.set(""),
        formState.message.set("Form cleared with Ctrl+F!"),
        clearInput("username-input"),
        clearInput("password-input")
    ],
    (KeyCode.S, 'ctrl'): alert("Document saved! (Ctrl+S)"),
})

app.add_page("index", index(), title="KeyCode Example", index=True)

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

Global Keyboard Shortcuts

Use addGlobalKeys() to create app-wide keyboard shortcuts that work anywhere on the page.

Why Global Shortcuts?

Global shortcuts are perfect for: - App-level commands (Save, Undo, Redo) - Navigation shortcuts - Quick actions that should work anywhere

Important: Always use modifier keys (Ctrl, Alt, etc.) with global shortcuts to avoid blocking normal typing in input fields.

Basic Usage

from dars.all import *

app = App("Global Shortcuts")

# Define your actions
def save_document():
    return alert("Document saved!")

def undo():
    return log("Undo action")

# Add global shortcuts
addGlobalKeys(app, {
    (KeyCode.S, 'ctrl'): save_document(),
    (KeyCode.Z, 'ctrl'): undo()
})

With Multiple Actions

formState = State("form", data="")

addGlobalKeys(app, {
    (KeyCode.ENTER, 'ctrl'): [
        formState.data.set("Submitted!"),
        alert("Form submitted with Ctrl+Enter")
    ],
    (KeyCode.ESCAPE, 'ctrl'): [
        formState.data.set(""),
        log("Form cleared")
    ]
})

Multiple Modifiers

addGlobalKeys(app, {
    (KeyCode.Z, 'ctrl'): undo(),
    (KeyCode.Z, 'ctrl', 'shift'): redo(),
    (KeyCode.S, 'ctrl', 'shift'): save_as()
})

Complete Example

from dars.all import *

app = App("Global Shortcuts Example")

docState = State("document", 
    content="", 
    saved=False,
    message="Ready"
)

@route("/")
def index():
    return Page(
        Container(
            Text("Document Editor", style="text-2xl font-bold"),
            Text(useDynamic("document.message"), style="text-slate-500"),

            Input(
                id="editor",
                placeholder="Start typing...",
                style="w-full min-h-[200px]"
            ),

            Container(
                style="mt-5 p-4 bg-slate-100",
                children=[
                    Text("Global Shortcuts:", style="font-bold"),
                    Text("• Ctrl+S - Save"),
                    Text("• Ctrl+Z - Undo"),
                    Text("• Ctrl+Shift+Z - Redo"),
                ]
            )
        )
    )

# Global keyboard shortcuts
addGlobalKeys(app, {
    (KeyCode.S, 'ctrl'): [
        docState.saved.set(True),
        docState.message.set("Document saved!"),
        alert("Saved!")
    ],
    (KeyCode.Z, 'ctrl'): [
        docState.message.set("Undo"),
        log("Undo action")
    ]
})

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

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

Best Practices

1. Use Modifiers for Global Shortcuts

Bad - Blocks typing:

addGlobalKeys(app, {
    KeyCode.ENTER: submit_form()  # Blocks Enter in all inputs!
})

Good - Doesn't interfere:

addGlobalKeys(app, {
    (KeyCode.ENTER, 'ctrl'): submit_form()  # Only Ctrl+Enter
})

2. Use onKey() for Component-Specific Keys

For specific components:

Input(
    id="search",
    on_key_press=onKey(KeyCode.ENTER, perform_search())
)

For app-wide shortcuts:

addGlobalKeys(app, {
    (KeyCode.F, 'ctrl'): focus("search")
})

3. Use switch() for Multiple Keys

Bad - Repetitive:

Input(
    on_key_press=onKey(KeyCode.ENTER, action1())
)
Input(
    on_key_press=onKey(KeyCode.ESCAPE, action2())
)

Good - Clean:

Container(
    on_key_press=switch({
        KeyCode.ENTER: action1(),
        KeyCode.ESCAPE: action2()
    })
)

4. Combine with State and V()

formState = State("form", username="")

Input(
    id="username",
    on_key_press=onKey(KeyCode.ENTER, formState.username.set(V("#username")))
)

Summary

  • Use on_key_press for all keyboard events (replaces on_key_down and on_key_up )
  • Use KeyCode constants for readable key references
  • Use onKey() for single key detection with optional modifiers
  • Use switch() for handling multiple different keys
  • Use addGlobalKeys() for app-wide shortcuts (always with modifiers!)
  • Combine with State and V() for dynamic, reactive keyboard interactions

Dars - Exporter Documentation

Introduction

Exporters are the heart of Dars that allow transforming applications written in Python to different technologies and platforms. Each exporter translates Dars components, styles, and scripts to the native code of the target platform.

Exporter Architecture

Base Exporter Class

All exporters inherit from the base Exporter class:

from abc import ABC, abstractmethod

class Exporter(ABC):
    def __init__(self):
        self.templates_path = "templates/"

    @abstractmethod
    def export(self, app: App, output_path: str) -> bool:
        """Exports the application to the specific format"""
        pass

    @abstractmethod
    def render_component(self, component: Component) -> str:
        """Renders an individual component"""
        pass

    @abstractmethod
    def get_platform(self) -> str:
        """Returns the name of the platform"""
        pass

Exportation Flow

  1. Validation : Verify that the application is valid
  2. Preparation : Create directory structure
  3. Rendering : Convert components to the target format
  4. Generation : Create configuration and dependency files
  5. Finalization : Write files to the system

Web Exporters

HTML/CSS/JavaScript

The HTML exporter generates standard web applications that can run in any browser.

Features

  • Compatibility : Works in all modern browsers
  • Simplicity : No requires build tools
  • Performance : Fast loading and efficient execution
  • SEO : Content indexable by search engines

Usage

dars export my_app.py --format html --output ./dist

Generated Structure

dist/
├── index.html      # Main page
├── styles.css      # CSS styles
└── script.js       # JavaScript logic

Example Output

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My Application</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div
      id="container_123"
      class="dars-container"
      style="display: flex; flex-direction: column; padding: 20px;"
    >
      <span
        id="text_456"
        class="dars-text"
        style="font-size: 24px; color: #333;"
        >Hello Dars!</span
      >
      <button
        id="button_789"
        class="dars-button"
        style="background-color: #007bff; color: white;"
      >
        Click
      </button>
    </div>
    <script src="script.js"></script>
  </body>
</html>

styles.css

/* Base Dars styles */
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  font-family:
    -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif;
}

.dars-button {
  display: inline-block;
  padding: 8px 16px;
  border: 1px solid #ccc;
  background-color: #f8f9fa;
  color: #333;
  cursor: pointer;
  border-radius: 4px;
  font-size: 14px;
}

.dars-button:hover {
  background-color: #e9ecef;
}

Advantages

  • Universality : Works in any web server
  • Debugging : Easy to debug with browser tools
  • Personalization : CSS and JavaScript completely modifiable
  • Hosting : Can be hosted on any static hosting service

Use Cases

  • Corporate websites
  • Landing pages
  • Simple web applications
  • Quick prototypes
  • Interactive documentation

Exporter Personalization

Extending Existing Exporters

from dars.exporters.base import Exporter

class MyCustomExporter(Exporter):
    def get_platform(self):
        return "my_custom_platform"

    def export(self, app, output_path):
        # Implement custom export logic
        return True

    def render_component(self, component):
        # Implement custom component rendering
        return "generated_code"

Exporting Web Applications

Dars currently supports exporting web applications using the standard web exporter. Your exported output includes optimized HTML, CSS, and JavaScript that can be hosted as static assets or served with FastAPI.

For advanced export scenarios, you can extend dars.exporters.base.Exporter and implement custom export behavior for your target platform.

Dars - Script & Action System

Dars Framework features a dual-layer logic system: Structured DAP Actions for high-security, high-performance interactions, and JavaScript Scripts for complex client-side logic.


The Dars Action Protocol (DAP)

DAP is the modern backbone of Dars interactivity. It replaces raw JavaScript strings with structured data objects that describe actions.

Why DAP?

  1. Security : DAP actions are executed by a built-in registry, avoiding dangerous eval() or new Function() calls. This enables a strict Content Security Policy (CSP).
  2. Predictability : Actions are defined as Python dictionaries, making them easier to debug and validate.
  • Full-Stack Consistency : The same action structure works in SPA and SSR targets.

dScript: The Universal Action Container

The dScript class is the primary way to define logic in Dars. It supports three modes:

Define actions using the data parameter. This is the most secure and modern approach.

from dars.all import dScript

# A structured DAP action
my_action = dScript(data={
    "op": "alert",
    "args": {"message": "Hello from DAP!"}
})

2. Inline JavaScript (Legacy/Complex)

For logic that DAP cannot yet express, you can use raw JS. Dars will attempt to convert this to DAP at compile-time.

script = dScript(code="console.log('Legacy JS');")

3. External File Reference

Load complex JS modules from your project files.

script = dScript(file_path="./scripts/my_module.js")

Advanced Logic Helpers

RawJS: Escaping String Literals

When you pass a string to a DAP helper (like this().state() ), Dars treats it as a text literal. Use RawJS to tell the compiler: "This is a JavaScript variable/expression".

from dars.scripts.dscript import RawJS
from dars.core.state import this

# Updates the component text with the value of a JS variable named 'myVar'
update_op = this().state(text=RawJS("myVar"))

The Arg Helper: Accessing Chained Results

When chaining scripts with .then() , the result of the previous script is passed to the next one. Use the Arg helper to access this value Pythonically. Arg is a singleton instance of _ArgHelper (a RawJS subclass).

from dars.scripts.dscript import Arg

# Example chaining: use the previous result in the next script
update_op = this().state(text=Arg) # Accesses the entire result
update_op_nested = this().state(text=Arg.content) # Accesses 'result.content'

chained = some_async_action.then(update_op)

Logic Chaining ( .then() )

All dScript and RawJS objects support the .then() method for sequential execution. This creates an asynchronous pipeline where values flow between steps.

from dars.all import *

action = (
    alert("Starting process...")
    .then(log("Process step 1"))
    .then(alert("Finished!"))
)

Utility Functions ( utils_ds )

Dars provides high-level Python helpers that return pre-configured, DAP-compatible dScript objects.

Interactive Utilities

Function Description
alert(msg) Shows a browser alert dialog.
log(msg) Logs a message to the browser console.
goTo(url) Navigates to a new URL in the same tab.
goToNew(url) Opens a URL in a new browser tab.
reload() Reloads the current page.
show(id) Makes an element visible ( display: block ).
hide(id) Hides an element ( display: none ).
toggle(id) Toggles an element's visibility.
setText(id, text) Sets the text content of an element.
addClass(id, name) Adds a CSS class to an element.
removeClass(id, name) Removes a CSS class.
toggleClass(id, name) Toggles a CSS class.
copyToClipboard(text) Copies the provided text to the system clipboard.

and a LOT more..

Animation System

Animations in Dars return dScript objects and can be combined using sequence() or chained with .then() .

from dars.all import fadeIn, pulse, sequence

# Animate an element when clicked
btn = Button("Animate", on_click=sequence(
    fadeIn("box", duration=300),
    pulse("box", scale=1.2)
))

Available Animations Reference

Function Purpose Key Parameters
fadeIn Fade element in id , duration , easing
fadeOut Fade element out id , duration , hide_after
slideIn Slide element in id , direction , duration
slideOut Slide element out id , direction , duration
scaleIn Scale element in id , from_scale , duration
shake Shake effect id , intensity , duration
pulse Pulse/heartbeat id , scale , iterations
rotate Rotate element id , degrees , duration
flip Flip on axis id , axis , duration

Best Practices

  1. Prefer DAP : Always use built-in helpers (like alert() , show() , this().state() ) instead of raw JS strings.
  2. Use RawJS for Variables : Only use RawJS when you explicitly need to reference a client-side JavaScript variable or expression.
  3. Chain for Flow : Use .then() to create logical sequences, keeping each step focused.
  4. Arg for Data : Use the Arg helper to handle results from asynchronous operations (like API calls or file reading) without writing JavaScript.