Welcome to the official Dars Framework documentation. Here you will find detailed guides and references to help you build modern web applications with Python.
To install Dars, simply use pip:
pip install dars-framework
This will install Dars and all its dependencies automatically.
You can install the official Dars Framework VS Code extension to have the dars dev tools.
Once installed, the
dars
command will be available in your terminal. You can use it to:
dars export my_app.py --format html --output ./my_app_web
dars preview ./my_app_web
# Basic project with Hello World
dars init my_new_project
# Project with a specific template
dars init my_project -t basic/Forms
dars info my_app.py
dars formats
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.
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={'padding': '20px'}) # Use ' to escape quotes
text = Text(text="Hello Dars!", style={'font-size': '24px'}) # Use ' to escape quotes
container.add_child(text)
app.set_root(container)
if __name__ == "__main__":
app.rTimeCompile()
Save the code above as
my_first_app.py
and then run:
dars export my_first_app.py --format html --output ./my_app
dars preview ./my_app
# General help
dars --help
# Application information
dars info my_app.py
# Available formats
dars formats
# Preview application
dars preview ./output_directory
pip install dars-framework
dars
works correctly (
dars --help
)
Congratulations! Dars is ready to use.
You can build native desktop apps from Dars projects. This capability is in BETA and is not recommended for production yet, but it is usable for testing.
# Scaffold or update a desktop-capable project
dars init --type desktop
# or
dars init --update
# Verify optional tooling (Node/Bun and packager)
dars doctor --all --yes
# Ensure your config sets the desktop format and target
# dars.config.json
{
"entry": "main.py",
"format": "desktop",
"outdir": "dist",
"targetPlatform": "auto"
}
# Build desktop artifacts
dars build
Notes: - Desktop support is under active development; configuration keys and defaults may change. - Some platform targets (like macOS) require building on that OS for signing.
Welcome to Dars, a modern Python framework for building web applications with reusable UI components.
There is an official Dars Framework extension for VS Code to have the dars dev tools.
Install Dars
See INSTALL section for installation instructions.
Explore Components
Discover all available UI components in
components.md
.
Command-Line Usage
Find CLI commands, options, and workflows in
cli.md
.
App Class Learn how to create an app class in App Documentation .
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")
# 9. Run app with preview
if __name__ == "__main__":
app.rTimeCompile()
app.rTimeCompile().add_file_types = ".js,.css"
Start building with Dars...
The Dars Command Line Interface (CLI) lets you manage your projects, export apps, and preview results quickly from the terminal.
Open your terminal in your project directory and use any of the following commands:
# Show information about your app
dars info my_app.py
# Export to different formats (web)
dars export my_app.py --format html --output ./output
# Skip default Python minifier for this run (does not affect viteMinify)
dars export my_app.py --format html --output ./output --no-minify
# 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
# Build using project config (dars.config.json)
dars build
# Build desktop (BETA) when format is desktop in config
dars build
# Build without the default Python minifier
dars build --no-minify
# Help
dars --help
# Version
dars -v
| 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 ./my_app_web
|
Preview exported app locally |
dars build
|
Build using dars.config.json |
dars init --type desktop
|
Scaffold desktop-capable project (BETA) |
dars build
(desktop config)
|
Build desktop app artifacts (BETA) |
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 --backend
|
Run only the configured backendEntry (FastAPI/SSR backend) |
dars info my_app.py
|
Show info about your app |
dars formats
|
List supported export formats |
dars --help
|
Show help and all CLI options |
Dars provides official templates to help you start new projects quickly. Templates include ready-to-use apps for forms, layouts, dashboards, multipage, and more.
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
Export the template to HTML/CSS/JS:
dars export main.py --format html --output ./hello_output
dars export main.py --format html --output ./dashboard_output
# ...etc
Preview the exported app:
dars preview ./hello_output
dars --help
for a full list of commands and options.
app.rTimeCompile()
) or from exported files with
dars preview
.
dars init my_project -t <template>
.
"format": "desktop"
in
dars.config.json
.
dars init --type desktop
(or
--update
) to scaffold backend files.
dars doctor --all --yes
to set up optional tooling.
dars build
. This feature is in BETA: suitable for testing, not yet for production.
For more, see the Getting Started guide and the main documentation index.
When working with SSR (
dars init --type ssr
), the workflow involves two processes:
dars dev
)
: Runs the Dars preview server on port
8000
using
app.rTimeCompile()
.
dars dev --backend
)
: Runs the FastAPI SSR backend on port
3000
using
backendEntry
from
dars.config.json
.
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
|
Starts the SSR/backend server defined by
backendEntry
.
|
dars build
|
Builds static assets to
dist/
for production.
|
Note: For production, you only need to run the backend (which serves the built assets).
The file (dars.config.json) configures how Dars exports and builds your project. It is created by
dars init <name>
for new projects and can be merged/updated in existing projects with
dars init --update
.
{
"entry": "main.py",
"format": "html",
"outdir": "dist",
"publicDir": null,
"include": [],
"exclude": ["**/__pycache__", ".git", ".venv", "node_modules"],
"bundle": false,
"defaultMinify": true,
"viteMinify": true,
"markdownHighlight": true,
"markdownHighlightTheme": "auto",
"utility_styles": {},
"backendEntry": "backend.api:app"
}
entry
Python entry file for your app. Used by
dars build
and by
dars export config
.
format
Export format. Supported:
html
and
desktop
(BETA). When set to
desktop
, the build command will produce native desktop artifacts.
outdir Directory where the exported files are written.
publicDir
Directory whose contents are copied as-is into the output (e.g.
public/
or
assets/
). If
null
, Dars will try to autodetect common locations.
include / exclude
Simple filters (by substring) applied when copying from
publicDir
.
bundle Reserved for future use. Current exporters already produce a bundled output.
defaultMinify Toggle the built-in Python minifier (safe and conservative). Controls HTML minification and provides JS/CSS fallback when advanced tools are unavailable.
true
(default): run the default Python-side minifier.
false
: skip the default minifier. You can still use Vite/esbuild via
viteMinify
.
viteMinify Toggle the advanced JS minifier.
true
(default): prefer the advanced minifier; fall back to the secondary minifier; if neither is available, a conservative built-in fallback is used.
false
: skip the advanced minifier and use the secondary minifier directly; fall back to the conservative built-in if not available.
utility_styles
Dictionary defining custom utility classes. Keys are class names, values are lists of utility strings or raw CSS properties.
Example:
"btn-primary": ["bg-blue-500", "text-white"]
markdownHighlight Auto-inject a client-side syntax highlighter for fenced code blocks in Markdown.
true
(default): injects Prism.js assets once per page and highlights
pre code
blocks.
false
: no assets injected; you can include your own highlighter or none at all.
backendEntry
Python import path for your FastAPI/SSR backend application (e.g.
"backend.api:app"
).
dars dev --backend
to start the backend server.
RouteType.SSR
,
dars config validate
will require this field to be present.
format
is
desktop
.
auto
(default),
windows
,
linux
,
macos
.
Desktop export is BETA: suitable for testing, not recommended for production yet. Configuration keys and defaults may change.
dars init --update
merges your existing config with Dars defaults and writes the result back, adding any new keys (like
defaultMinify
,
viteMinify
) without removing your current settings.
dars export
and
dars build
, Dars reads this file and configures the minification pipeline accordingly.
dars build
, a small notice may appear indicating that a less powerful minifier was used.
--no-minify
(does not affect
viteMinify
).
To add or refresh the config in an existing project:
dars init --update
To review optional tooling that can enhance bundling/minification, run:
dars doctor
If you want to force using only the secondary minifier, set
"viteMinify": false
.
"defaultMinify": false
; to disable it per-run use
--no-minify
.
Dars provides a simple, built-in way to detect the current running environment (Development vs Production) through the
DarsEnv
class.
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).
The
DarsEnv
class is available directly from
dars.env
:
from dars.env import DarsEnv
DarsEnv.dev
A boolean property that indicates if the application is running in development mode.
True
: When running via
dars dev
(where bundling is disabled by default).
False
: When running via
dars build
or
dars export
(production builds).
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")
)
)
You can also use it to toggle configuration values:
api_url = "http://localhost:3000" if DarsEnv.dev else "https://api.myapp.com"
Note :
DarsEnvis initialized automatically by the Dars CLI tools. You do not need to configure it manually.
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.
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
):
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.
target
can be:
Button("OK", id="ok")
).
str
id of an existing component in the app tree to move it.
root
: Where to insert. Accepts:
str
).
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
.
app.root
) and multipage (
app.add_page
) setups.
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")
id
.
# 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.
The
App
class now includes an enhanced
add_global_style()
method that supports both inline style definitions and external CSS file imports.
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")
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;
}
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")
The enhanced method maintains full backward compatibility - existing code using
add_global_style(selector, styles)
will continue to work without modification.
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')
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
The
HTMLCSSJSExporter
uses the PWA configuration from the App class to generate:
{
"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"
}
]
}
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);
});
});
}
# 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")
Dars Framework's PWA implementation is compatible with: - Chrome/Chromium (full support) - Firefox (basic support) - Safari (limited support on iOS) - Edge (full support)
You can now import all main components and modules with a single line:
from dars.all import *
This simplifies integration and improves the developer experience.
But if you crate your own components it can cause conflicts with names of your components and built in components if they have the same name.
Components are the fundamental elements of Dars that represent UI elements. Each component encapsulates its appearance, behavior, and state, allowing you to create complex interfaces by composing simple elements.
To learn how to create your own custom components, refer to the documentation in Custom Components .
Dars provides a powerful way to handle user interactions through the
dScript
class. You can attach event handlers to interactive components like
Button
and
Input
to create dynamic and responsive user interfaces.
For a complete list of available event types and how to use them, refer to the documentation in Events .
You can create or delete components dynamically at runtime in the browser using
createComp()
and
deleteComp()
. These functions return
dScript
so you can attach them to events.
from dars.all import *
exposes
createComp
and
deleteComp
.
id
also receives a CSS class
dars-id-<id>
to help target multiple instances when needed.
createComp(target, root, position='append') -> dScript
deleteComp(id) -> dScript
root
.
append
(default)
prepend
before:<id>
(insert before a reference sibling id)
after:<id>
(insert after a reference sibling id)
from dars.all import *
container = Container(id="root")
# Add a button that creates a new Text inside #root on click
add_btn = Button(
text="Add",
id="add",
on_click=createComp(Text("Hi", id="msg"), root="root", position='append')
)
# Add another button that inserts before a specific sibling
insert_btn = Button(
text="Insert Before",
on_click=createComp(Text("Before", id="before"), root="root", position='before:msg')
)
# Button that deletes an element by id
delete_btn = Button(
text="Delete msg",
on_click=deleteComp("msg")
)
Dars components (including
@FunctionComponent
) support
lifecycle hooks
that run in the browser:
onMount
: runs once when the component is registered in the runtime and mounted into the DOM.
onUpdate
: runs after dynamic changes that affect the component, for example:
Dars.change({ id, dynamic: true, ... })
(used internally by
updateComp
).
updateVRef()
+
Dars.updateVRef
).
onUnmount
: runs right before the component is removed from the DOM (for example, via
deleteComp
).
Hooks accept
dScript
or inline JS strings and are passed as component props.
from dars.all import *
@route("/")
def index():
counter_box = Container(
Text("Dynamic counter: ", id="dyn_label"),
Text(setVRef(0, ".dyn_count")),
id="dyn_box",
class_name="p-4 border rounded mb-2",
onMount=dScript("console.log('[Lifecycle] dyn_box mounted');"),
onUpdate=dScript("console.log('[Lifecycle] dyn_box updated');"),
onUnmount=dScript("console.log('[Lifecycle] dyn_box unmounted');"),
)
host_id = "dyn_host"
return Page(
Container(
Container(id=host_id),
# Dynamically create the component with lifecycle and VRefs
Button(
"Create",
on_click=createComp(counter_box, host_id, position="append"),
),
# Update the value using V() + updateVRef (triggers onUpdate)
Button(
"Increment",
on_click=updateVRef(
".dyn_count",
V(".dyn_count").int() + 1,
),
),
# Delete the component (triggers onUnmount)
Button(
"Delete",
on_click=deleteComp("dyn_box"),
),
)
)
Initial render / hydration :
lifecycle
block per node with
onMount
,
onUpdate
,
onUnmount
.
DarsHydrate
and runs
onMount
once per id.
createComp / deleteComp :
createComp
uses
VDomBuilder
to include lifecycle and events in the dynamic VDOM.
runtime.createComponent
registers lifecycle for the subtree and runs
onMount
after inserting it into the DOM.
deleteComp
causes
runtime.deleteComponent
to run
onUnmount
before removing the node.
Dynamic changes and VRefs :
updateComp()
generates calls to
Dars.change({ id, dynamic: true, ... })
, which eventually call
onUpdate
for that id.
updateVRef(selector, ...)
updates DOM and VRefs, then calls
Dars.updateVRef(selector)
, which finds the nearest lifecycle-enabled ancestor for each affected element and runs its
onUpdate
.
This allows you to attach side effects (logs, external integrations, controlled side-effects) to your components' lifecycle, both for static components and those created/removed at runtime.
from dars.all import *
# Button with click handler
button = Button(
text="Click me",
on_click=dScript("""
function handleClick(event) {
alert('Button was clicked!');
// Access the button element
const button = event.target;
// Toggle a class on click
button.classList.toggle('clicked');
}
""")
)
# Input with change handler
input_field = Input(
placeholder="Type something...",
on_change=dScript("""
function handleChange(event) {
console.log('Input value changed to:', event.target.value);
// Add validation or other logic here
if (event.target.value.length < 3) {
event.target.style.borderColor = 'red';
} else {
event.target.style.borderColor = 'green';
}
}
"""
)
| Component | Event | Description |
|---|---|---|
Button
|
on_click
|
Triggered when the button is clicked |
Button
|
on_double_click
|
Triggered on double click |
Button
|
on_mouse_enter
|
Triggered when mouse enters the button |
Button
|
on_mouse_leave
|
Triggered when mouse leaves the button |
Input
|
on_change
|
Triggered when input value changes |
Input
|
on_key_up
|
Triggered when a key is released |
Input
|
on_key_press
|
Triggered when a key is pressed |
target
,
keyCode
, etc.
false
to prevent default behavior when needed.
All components in Dars inherit from the base
Component
class, which provides common functionality:
from dars.core.component import Component
class Component(ABC):
def __init__(self, **props):
self.props = props
self.children: List[Component] = []
self.parent: Optional[Component] = None
self.id: Optional[str] = props.get('id')
self.class_name: str = props.get("class_name", self.__class__.__name__)
self.style: Dict[str, Any] = props.get('style', {})
self.hover_style: Dict[str, Any] = props.get('hover_style', {})
self.active_style: Dict[str, Any] = props.get('active_style', {})
self.events: Dict[str, Callable] = {}
self.key: Optional[str] = props.get('key')
All components support these basic properties:
on_resize event handler receives a dScript object or a comp.state() function
children : List of child components (for containers)
All components include a powerful search and modification system through the
find()
method. This allows you to search for components in the component tree and modify their attributes using a fluent interface.
# Find by ID
component.find(id="search-button")
# Find by CSS class
component.find(class_name="primary-button")
# Find by component type
component.find(type="Button") # or type=Button
# Find using a custom predicate
component.find(predicate=lambda c: "welcome" in c.text.lower())
You can chain multiple
find()
calls to search within the results of previous searches:
# Find a container and then search within it
component.find(id="main-container")\
.find(type="Text")\
.attr(text="New text")
# Multiple levels of search
component.find(class_name="section")\
.find(type="Container")\
.find(id="special-text")\
.attr(text="Modified text")
Use the
attr()
method to modify the found components:
# Modify styles
component.find(type="Button").attr(
style={"background-color": "red", "color": "white"}
)
# Modify class names
component.find(class_name="btn").attr(
class_name="btn primary"
)
# Modify component-specific attributes
component.find(type="Text").attr(
text="New content"
)
# Multiple modifications at once
component.find(type="Input").attr(
placeholder="Type here...",
style={"padding": "10px"},
class_name="modern-input"
)
# Get all matched components
components = component.find(type="Button").get()
# Get only the first match
first_button = component.find(type="Button").first()
| Parameter | Type | Description | Example |
|---|---|---|---|
id
|
str | Search by component ID |
find(id="search-btn")
|
class_name
|
str | Search by CSS class |
find(class_name="primary")
|
type
|
str/Type | Search by component type |
find(type="Button")
or
find(type=Button)
|
predicate
|
Callable | Custom search function |
find(predicate=lambda c: len(c.children) > 0)
|
The
Page
component represents the root of a multipage app. It can contain other components and scripts specific to that page.
from dars.components.basic import Page, Text, Button
from dars.scripts.script import InlineScript
page = Page(
Text("Bienvenido!"),
Button("Click aquí", id="btn-demo")
)
# Añadir script JS solo a esta página
page.add_script(InlineScript("""
document.addEventListener('DOMContentLoaded', function() {
var btn = document.getElementById('btn-demo');
if (btn) btn.onclick = () => alert('¡Botón de esta página!');
});
"""))
Use
Page
as the root of each page in the multipage system. Allows passing children directly as arguments and JS scripts per page.
| Property | Type | Description |
|---|---|---|
children
|
list | List of child components |
anchors
|
dict | Optional anchor points for child placement |
The new page scripts system allows assigning scripts to specific pages instead of globally:
add_script()
method on a page instance.
from dars.scripts.dscript import dScript
index.add_script(
dScript(code="console.log('Hello world')")
)
The
Head
component allows you to manage the
<head>
section of your page, including the title, meta tags, and links. It supports SEO metadata like Open Graph and Twitter Cards.
from dars.components.advanced.head import Head
head = Head(
title="My Page Title",
description="This is a description for SEO.",
keywords="dars, framework, python",
og_title="My Open Graph Title",
twitter_card="summary_large_image"
)
| Property | Type | Description |
|---|---|---|
title
|
str |
The page title (
<title>
)
|
description
|
str | Meta description |
keywords
|
str/list | Meta keywords |
author
|
str | Meta author |
robots
|
str | Robots meta tag |
canonical
|
str | Canonical URL link |
favicon
|
str | Favicon URL link |
og_title
|
str | Open Graph title |
og_description
|
str | Open Graph description |
og_image
|
str | Open Graph image URL |
og_type
|
str | Open Graph type (default: "website") |
twitter_card
|
str | Twitter card type (default: "summary") |
twitter_site
|
str | Twitter site handle |
twitter_creator
|
str | Twitter creator handle |
meta
|
dict |
Custom meta tags
{name: content}
|
links
|
list |
Custom link tags
[{rel: ..., href: ...}]
|
structured_data
|
dict | JSON-LD structured data |
The
Text
component displays static or dynamic text.
from dars.components.basic.text import Text
text = Text(
text="Contenido del text",
id="mi-text",
class_name="text-principal",
style={
"font-size": "16px",
"color": "#333",
"font-weight": "bold"
}
)
| Property | Type | Description | Example |
|---|---|---|---|
text
|
str | Text content |
"Hello world"
|
id
|
str | Unique identifier |
"title-primary"
|
class_name
|
str | CSS class |
"text-highlight"
|
style
|
dict | CSS styles |
{"color": "red"}
|
# Título principal
title = Text(
text="Título Principal",
style={
"font-size": "32px",
"font-weight": "bold",
"color": "#2c3e50",
"margin-bottom": "20px",
"text-align": "center"
}
)
# Párrafo de contenido
paragraph = Text(
text="Este es un párrafo de ejemplo con contenido descriptivo.",
style={
"font-size": "16px",
"line-height": "1.6",
"color": "#34495e",
"margin-bottom": "15px"
}
)
# Texto pequeño
note = Text(
text="Nota: Esta información es importante.",
style={
"font-size": "12px",
"color": "#7f8c8d",
"font-style": "italic"
}
)
The
Button
component creates interactive buttons that can execute actions.
from dars.components.basic.button import Button
boton = Button(
text="Hacer clic",
button_type="button", # "button", "submit", "reset"
disabled=False,
on_click=dScript("""
function handleClick() {
alert('Button clicked!');
}
""")
style={
"background-color": "#3498db",
"color": "white",
"padding": "10px 20px",
"border": "none",
"border-radius": "4px"
}
)
| Property | Type | Description | Values |
|---|---|---|---|
text
|
str | Button text |
"Enviar"
|
button_type
|
str | Button type |
"button"
,
"submit"
,
"reset"
|
disabled
|
bool | Si está deshabilitado |
True
,
False
|
on_click
|
dScript | Click handler |
dScript("function() { ... }")
|
on_double_click
|
dScript | Double click handler |
dScript("function() { ... }")
|
on_mouse_enter
|
dScript | Mouse enter handler |
dScript("function() { ... }")
|
on_mouse_leave
|
dScript | Mouse leave handler |
dScript("function() { ... }")
|
on_key_up
|
dScript | Key up handler |
dScript("function(e) { ... }")
|
on_key_press
|
dScript | Key press handler |
onKey(KeyCode.ENTER, action)
|
# Primary Button
primary_button = Button(
text="Acción Principal",
style={
"background-color": "#007bff",
"color": "white",
"padding": "12px 24px",
"border": "none",
"border-radius": "6px",
"font-size": "16px",
"font-weight": "500",
"cursor": "pointer",
"transition": "background-color 0.3s"
}
)
# Secondary Button
secondary_button = Button(
text="Cancelar",
style={
"background-color": "transparent",
"color": "#6c757d",
"padding": "12px 24px",
"border": "1px solid #6c757d",
"border-radius": "6px",
"font-size": "16px",
"cursor": "pointer"
}
)
# Danger Button
delete_button = Button(
text="Eliminar",
style={
"background-color": "#dc3545",
"color": "white",
"padding": "8px 16px",
"border": "none",
"border-radius": "4px",
"font-size": "14px"
}
)
# Disabled Button
disabled_button = Button(
text="No disponible",
disabled=True,
style={
"background-color": "#e9ecef",
"color": "#6c757d",
"padding": "10px 20px",
"border": "none",
"border-radius": "4px",
"cursor": "not-allowed"
}
)
The
Input
component allows user data entry.
from dars.components.basic.input import Input
entrada = Input(
value="Valor inicial",
placeholder="Escribe aquí...",
input_type="text", # "text", "password", "email", "number", etc.
disabled=False,
readonly=False,
required=False,
max_length=100,
on_change=dScript("""
function handleChange(event) {
console.log('Input changed:', event.target.value);
}
""")
style={
"width": "300px",
"padding": "10px",
"border": "1px solid #ddd",
"border-radius": "4px"
}
)
| Property | Type | Description | Values |
|---|---|---|---|
value
|
str | Initial value |
"text"
|
placeholder
|
str | Help text |
"Ingresa tu name"
|
input_type
|
str | Tipo de entrada |
"text"
,
"password"
,
"email"
,
"number"
|
disabled
|
bool | Si está deshabilitado |
True
,
False
|
readonly
|
bool | Solo lectura |
True
,
False
|
required
|
bool | Campo obligatorio |
True
,
False
|
on_change
|
dScript | Change handler |
dScript("function(e) { ... }")
|
on_key_up
|
dScript | Key up handler |
dScript("function(e) { ... }")
|
on_key_press
|
dScript | Key press handler |
onKey(KeyCode.ENTER, action)
|
max_length
|
int | Longitud máxima |
50
|
min_length
|
int | Longitud mínima |
3
|
pattern
|
str | Validation pattern |
"[0-9]+"
|
# Basic text input
name = Input(
placeholder="Ingresa tu name",
input_type="text",
required=True,
style={
"width": "100%",
"padding": "12px",
"border": "2px solid #e1e5e9",
"border-radius": "8px",
"font-size": "16px"
}
)
# Email input
email = Input(
placeholder="tu@email.com",
input_type="email",
required=True,
style={
"width": "100%",
"padding": "12px",
"border": "2px solid #e1e5e9",
"border-radius": "8px"
}
)
# Password input
password = Input(
placeholder="Contraseña",
input_type="password",
required=True,
min_length=8,
style={
"width": "100%",
"padding": "12px",
"border": "2px solid #e1e5e9",
"border-radius": "8px"
}
)
# Numeric input
edad = Input(
placeholder="Edad",
input_type="number",
style={
"width": "100px",
"padding": "8px",
"border": "1px solid #ccc",
"border-radius": "4px",
"text-align": "center"
}
)
# Search input
busqueda = Input(
placeholder="Buscar...",
input_type="search",
style={
"width": "300px",
"padding": "10px 15px",
"border": "1px solid #ddd",
"border-radius": "20px",
"background-color": "#f8f9fa"
}
)
The
Container
component is a container that can hold other components. It supports multiple ways to add child components.
from dars.components.basic.container import Container
# Method 1: Pass components as arguments
container = Container(
Text("Hello"),
Button("Click me"),
style={
"display": "flex",
"flex-direction": "column",
"padding": "20px",
"background-color": "#f8f9fa"
}
)
# Method 2: Use additional_children parameter
components = [Text("Hello"), Button("Click me")]
container = Container(
additional_children=components,
style={
"display": "flex",
"flex-direction": "column",
"padding": "20px",
"background-color": "#f8f9fa"
}
)
# Method 3: Add children after creation
container = Container(style={
"display": "flex",
"flex-direction": "column",
"padding": "20px",
"background-color": "#f8f9fa"
})
container.add_child(Text("Hello"))
container.add_child(Button("Click me"))
| Property | Type | Description |
|---|---|---|
children
|
tuple | Components passed as positional arguments |
additional_children
|
list | Optional list of additional components |
# Vertical layout (column)
columna = Container(
style={
"display": "flex",
"flex-direction": "column",
"gap": "15px",
"padding": "20px"
}
)
# Horizontal layout (row)
fila = Container(
style={
"display": "flex",
"flex-direction": "row",
"gap": "20px",
"align-items": "center"
}
)
# Layout centrado
centrado = Container(
style={
"display": "flex",
"justify-content": "center",
"align-items": "center",
"min-height": "100vh",
"background-color": "#f0f2f5"
}
)
# Card/Tarjeta
tarjeta = Container(
style={
"background-color": "white",
"border-radius": "12px",
"padding": "24px",
"box-shadow": "0 2px 10px rgba(0,0,0,0.1)",
"max-width": "400px",
"margin": "20px auto"
}
)
# Sidebar
sidebar = Container(
style={
"width": "250px",
"height": "100vh",
"background-color": "#2c3e50",
"padding": "20px",
"position": "fixed",
"left": "0",
"top": "0"
}
)
The
Section
component is a container that can hold other components. It supports multiple ways to add child components.
And also its like the
Container
component but instead of export a
<div>
to render it exports and
<section>
.
from dars.all import *
# Method 1: Pass components as arguments
container = Section(
Text("Hello"),
Button("Click me"),
style={
"display": "flex",
"flex-direction": "column",
"padding": "20px",
"background-color": "#f8f9fa"
}
)
# Method 2: Use additional_children parameter
components = [Text("Hello"), Button("Click me")]
container = Section(
additional_children=components,
style={
"display": "flex",
"flex-direction": "column",
"padding": "20px",
"background-color": "#f8f9fa"
}
)
# Method 3: Add children after creation
container = Section(style={
"display": "flex",
"flex-direction": "column",
"padding": "20px",
"background-color": "#f8f9fa"
})
container.add_child(Text("Hello"))
container.add_child(Button("Click me"))
| Property | Type | Description |
|---|---|---|
children
|
tuple | Components passed as positional arguments |
additional_children
|
list | Optional list of additional components |
The
Video
component is an advanced wrapper over the HTML5
<video>
element.
It supports static usage and full reactivity via
State
+
useDynamic
.
from dars.all import *
from dars.hooks.value_helpers import V
media_state = State(
"media",
current_video="/media/intro.mp4",
autoplay_video=False,
muted_video=True,
)
Video(
src=useDynamic("media.current_video"),
poster="/media/poster.jpg",
width="720",
controls=True,
autoplay=useDynamic("media.autoplay_video"),
muted=useDynamic("media.muted_video"),
preload="metadata",
class_name="rounded-[8px] shadow-[0_0_20px_rgba(0,0,0,0.4)] mb-[16px]",
attrs={
"controlsList": "nodownload",
},
)
| Property | Type | Description | Example |
|---|---|---|---|
src
|
str / useDynamic | Video source URL (relative or absolute) |
"/media/intro.mp4"
|
poster
|
str | Poster image URL |
"/media/poster.jpg"
|
width
|
str | Width attribute |
"720"
,
"100%"
|
height
|
str | Height attribute |
"480"
|
controls
|
bool / useDynamic | Show native controls |
True
,
useDynamic("media.show_controls")
|
autoplay
|
bool / useDynamic | Autoplay video when ready |
False
,
useDynamic("media.autoplay_video")
|
loop
|
bool / useDynamic | Loop playback |
useDynamic("media.loop_video")
|
muted
|
bool / useDynamic | Start muted |
useDynamic("media.muted_video")
|
preload
|
str |
Preload hint (
auto
,
metadata
,
none
)
|
"metadata"
|
plays_inline
|
bool | Hint for inline playback on mobile |
True
|
class_name
|
str | CSS class name |
"my-video"
|
style
|
dict / str | Inline styles |
{ "max-width": "100%" }
|
attrs
|
dict | Extra raw attributes |
{ "controlsList": "nodownload" }
|
src
,
autoplay
,
muted
,
loop
,
controls
and
plays_inline
support
useDynamic
.
State
changes, the exporter generates bindings that:
src
attribute directly.
autoplay
,
muted
,
loop
,
controls
), add/remove the HTML attribute and sync the DOM property.
controls=True
,
plays_inline=True
by default.
autoplay
,
loop
,
muted
are
off
by default unless you pass
True
or a state value.
from dars.all import *
from dars.hooks.value_helpers import V
media_state = State(
"media",
current_video="/media/intro.mp4",
autoplay_video=False,
muted_video=True,
)
@route("/", index=True)
def index():
return Page(
Container(
Video(
src=useDynamic("media.current_video"),
poster="/media/poster.jpg",
width="720",
controls=True,
autoplay=useDynamic("media.autoplay_video"),
muted=useDynamic("media.muted_video"),
preload="metadata",
),
Button(
"Toggle Mute",
on_click=media_state.muted_video.set(
(V("media.muted_video").bool() == True).then(False, True)
),
),
Button(
"Toggle Autoplay",
on_click=media_state.autoplay_video.set(
(V("media.autoplay_video").bool() == True).then(False, True)
),
),
)
)
The
Audio
component wraps the HTML5
<audio>
element and supports the same reactive model.
from dars.all import *
from dars.hooks.value_helpers import V
media_state = State(
"media",
current_audio="/media/theme1.mp3",
loop_audio=True,
)
Audio(
src=useDynamic("media.current_audio"),
controls=True,
autoplay=False,
loop=useDynamic("media.loop_audio"),
preload="auto",
class_name="w-[100%] mb-[12px]",
attrs={"controlsList": "nodownload"},
)
| Property | Type | Description | Example |
|---|---|---|---|
src
|
str / useDynamic | Audio source URL |
"/media/theme1.mp3"
|
controls
|
bool / useDynamic | Show native controls |
True
|
autoplay
|
bool / useDynamic | Autoplay audio |
useDynamic("media.autoplay_audio")
|
loop
|
bool / useDynamic | Loop playback |
useDynamic("media.loop_audio")
|
muted
|
bool / useDynamic | Start muted |
True
|
preload
|
str |
Preload hint (
auto
,
metadata
,
none
)
|
"auto"
|
class_name
|
str | CSS class |
"audio-player"
|
style
|
dict / str | Inline styles |
{ "width": "100%" }
|
attrs
|
dict | Extra attributes |
{ "controlsList": "nodownload" }
|
src
,
loop
,
autoplay
,
muted
,
controls
support
useDynamic
.
el.loop
,
el.muted
, etc.).
media/
en el root del proyecto y referenciarlos como
/media/...
.
El exporter copiará automáticamente esa carpeta al directorio de export.
@route("/audio-demo")
def audio_demo():
return Page(
Container(
Text("Audio actual:"),
Text(useDynamic("media.current_audio")),
Audio(
src=useDynamic("media.current_audio"),
controls=True,
loop=useDynamic("media.loop_audio"),
preload="auto",
),
Container(
Button(
"Track 1",
on_click=media_state.current_audio.set("/media/theme1.mp3"),
),
Button(
"Track 2",
on_click=media_state.current_audio.set("/media/theme2.mp3"),
),
Button(
"Toggle Loop",
on_click=media_state.loop_audio.set(
(V("media.loop_audio").bool() == True).then(False, True)
),
),
),
)
)
The
Markdown
component allows you to render markdown content directly in your Dars applications, converting markdown syntax to beautiful HTML with proper styling.
from dars.components.basic.markdown import Markdown
# From string content
markdown_component = Markdown(
content="# Welcome\nThis is **markdown** content",
id="my-markdown",
class_name="custom-markdown",
style={"padding": "20px"}
)
# From file
markdown_from_file = Markdown(
file_path="README.md",
id="documentation",
dark_theme=True
)
| Property | Type | Description | Example |
|---|---|---|---|
content
|
str | Markdown content as string |
"# Heading"
|
file_path
|
str | Path to a markdown file |
"docs/intro.md"
|
dark_theme
|
bool | Enable dark theme styling |
True
|
id
|
str | Component ID |
"markdown-content"
|
class_name
|
str | CSS class |
"markdown-body"
|
style
|
dict | CSS styles |
{"fontSize": "16px"}
|
| Method | Description | Example |
|---|---|---|
update_content(new_content=None, new_file_path=None)
|
Update markdown content |
markdown_component.update_content(new_content="# New")
|
set_dark_theme(enabled=True)
|
Enable/disable dark theme |
markdown_component.set_dark_theme(True)
|
# Simple markdown from string
simple_md = Markdown(
content="# Hello\nThis is a **markdown** example",
style={"maxWidth": "800px", "margin": "0 auto"}
)
# Load from file with dark theme
docs_md = Markdown(
file_path="documentation.md",
dark_theme=True,
class_name="docs-content"
)
# Update content dynamically
simple_md.update_content(new_content="# Updated\nNew content here")
The Markdown renderer supports fenced code blocks and emits standard
language-<lang>
classes, e.g.
language-python
.
By default, the exporter auto-injects a client-side highlighter (highlight.js) once per page and highlights all
pre code
blocks. This is controlled by
markdownHighlight
in
dars.config.json
.
Config example:
{
"markdownHighlight": true
}
true
(default), highlight.js CSS/JS + init are added automatically.
false
, no assets are injected; include your own highlighter if you want colored code.
Fenced code example:
```python
import time
def hello():
print("hi")
```
The Markdown component requires the
markdown2
library. Included with the framework.
#
,
##
,
###
)
Inline code
and code blocks
The Markdown component includes comprehensive default styling for both light and dark themes:
# Light theme (default)
markdown_light = Markdown(content="# Light theme")
# Dark theme
markdown_dark = Markdown(
content="# Dark theme",
dark_theme=True,
style={"padding": "20px", "borderRadius": "8px"}
)
from dars.core.app import App
from dars.components.basic.markdown import Markdown
from dars.components.basic.container import Container
app = App(title="Documentation Viewer")
# Load documentation from file
docs = Markdown(
file_path="README.md",
dark_theme=True,
class_name="documentation",
style={
"maxWidth": "800px",
"margin": "0 auto",
"padding": "40px",
"lineHeight": "1.6"
}
)
app.set_root(Container(children=[docs]))
This component is perfect for creating documentation pages, blog posts, content management systems, and any application that needs to display formatted text content.
The
Image
component displays images.
from dars.components.basic.image import Image
image = Image(
src="path/to/your/image.jpg",
alt="Descripción de la image",
width="300px",
height="200px",
class_name="responsive-img",
style={
"border-radius": "8px",
"box-shadow": "0 4px 8px rgba(0,0,0,0.1)"
}
)
| Property | Type | Description | Example |
|---|---|---|---|
src
|
str | Image path |
"images/logo.png"
|
alt
|
str | Alternative text |
"Logo of the company"
|
width
|
str | Ancho de la image (CSS) |
"100%"
,
"200px"
|
height
|
str | Alto de la image (CSS) |
"auto"
,
"150px"
|
The
Link
component creates navigation links.
from dars.components.basic.link import Link
link = Link(
text="Visitar Google",
href="https://www.google.com",
target="_blank", # Abre en una nueva pestaña
class_name="external-link",
style={
"color": "#007bff",
"text-decoration": "none",
"font-weight": "bold"
}
)
| Property | Type | Description | Values |
|---|---|---|---|
text
|
str | Link text |
"Ir a la página"
|
href
|
str | URL of destination |
"/about"
,
"https://example.com"
|
target
|
str | Dónde abrir el link |
"_self"
(misma pestaña),
"_blank"
(nueva pestaña)
|
The
Textarea
component allows for multi-line text input.
from dars.components.basic.textarea import Textarea
area_text = Textarea(
value="Texto inicial",
placeholder="Escribe tu mensaje aquí...",
rows=5,
cols=40,
disabled=False,
readonly=False,
required=True,
max_length=500,
class_name="comment-box",
style={
"width": "100%",
"padding": "10px",
"border": "1px solid #ccc",
"border-radius": "5px"
}
)
| Property | Type | Description | Values |
|---|---|---|---|
value
|
str | Initial value |
""
|
placeholder
|
str | Help text |
"Escribe aquí..."
|
rows
|
int | Número de filas visibles |
4
|
cols
|
int | Número de columnas visibles |
50
|
disabled
|
bool | Si está deshabilitado |
True
,
False
|
readonly
|
bool | Solo lectura |
True
,
False
|
required
|
bool | Campo obligatorio |
True
,
False
|
max_length
|
int | Longitud máxima |
500
|
The
FileUpload
component allows users to select files for uploading. It wraps a standard file input with a custom, styleable interface.
from dars.components.advanced.file_upload import FileUpload
upload = FileUpload(
id="doc-upload",
label="Choose a file...",
accept=".pdf,.doc,.docx",
multiple=False,
disabled=False,
required=True,
on_change=log("File uploaded"),
style="m-0"
)
| Property | Type | Description | Values |
|---|---|---|---|
label
|
str | Text displayed on the button |
"Upload"
|
accept
|
str | File types to accept |
".jpg,.png"
|
multiple
|
bool | Allow multiple files |
True
,
False
|
disabled
|
bool | Disable input |
True
,
False
|
required
|
bool | Mark as required |
True
,
False
|
on_change
|
Callable | Change handler |
log(...)
|
The
Chart
component renders interactive charts using Plotly.js.
from dars.components.visualization.chart import Chart
import plotly.graph_objects as go
fig = go.Figure(data=[go.Bar(x=['A', 'B', 'C'], y=[10, 20, 15])])
chart = Chart(
figure=fig,
width=800,
height=400,
style={"margin": "20px auto"}
)
| Property | Type | Description |
|---|---|---|
figure
|
plotly.graph_objects.Figure | Plotly figure object |
width
|
int/str | Width in pixels or CSS value |
height
|
int/str | Height in pixels or CSS value |
config
|
dict | Plotly configuration options |
The
DataTable
component displays tabular data with optional Pandas DataFrame support.
from dars.components.visualization.table import DataTable
# Using list of dicts (recommended)
data = [
{'Name': 'Alice', 'Age': 30, 'City': 'New York'},
{'Name': 'Bob', 'Age': 25, 'City': 'London'}
]
table = DataTable(data, theme='dark')
# With custom columns and formatters
data = [{'price': 99.99, 'qty': 5}, {'price': 149.99, 'qty': 3}]
table = DataTable(
data,
columns=[
{'key': 'price', 'label': 'Price', 'formatter': lambda x: f'${x:.2f}'},
{'key': 'qty', 'label': 'Quantity', 'align': 'center'}
],
striped=True,
hover=True
)
| Property | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame/list | Data source | - |
columns
|
list | Column definitions | Auto-inferred |
index
|
bool | Show DataFrame index |
False
|
header
|
bool | Show header row |
True
|
striped
|
bool | Alternating row colors |
True
|
hover
|
bool | Hover effects |
True
|
bordered
|
bool | Cell borders |
True
|
compact
|
bool | Compact spacing |
False
|
theme
|
str/dict | 'light', 'dark', or custom |
'light'
|
The
ProgressBar
component visually displays progress for a task, such as loading or completion percentage.
from dars.components.basic.progressbar import ProgressBar
progress = ProgressBar(value=40, max_value=100)
| Property | Type | Description |
|---|---|---|
value
|
int | Current progress value |
max_value
|
int | Maximum value (default: 100) |
progress = ProgressBar(value=75, max_value=100)
The
Tooltip
component displays a tooltip when hovering over a child component.
from dars.components.basic.tooltip import Tooltip
from dars.components.basic.button import Button
tooltip = Tooltip(
text="More info",
child=Button(text="Hover me")
)
| Property | Type | Description |
|---|---|---|
text
|
str | Tooltip text |
child
|
Component | Component to wrap |
position
|
str | Tooltip position (e.g., "top") |
tooltip = Tooltip(text="Help", child=Button(text="?"))
The
Accordion
component creates a vertically stacked set of expandable/collapsible panels for organizing content.
from dars.components.advanced.accordion import Accordion
accordion = Accordion(
items=[
{"title": "Section 1", "content": "Content for section 1"},
{"title": "Section 2", "content": "Content for section 2"}
],
allow_multiple=False
)
| Property | Type | Description |
|---|---|---|
items
|
list |
List of dicts with
title
and
content
|
allow_multiple
|
bool | Allow multiple sections open at once |
accordion = Accordion(
items=[
{"title": "FAQ 1", "content": "Answer 1"},
{"title": "FAQ 2", "content": "Answer 2"}
]
)
The
Tabs
component allows navigation between different views or content panels.
New in 1.0.5: The exporter now recursively detects Tabs at any nesting level (including inside containers, panels, or multipage apps) for
minimum_logicand JS injection. You can safely nest Tabs in any structure and the export will work as expected.
from dars.components.advanced.tabs import Tabs
tabs = Tabs(
tabs=[
{"label": "Tab 1", "content": "Content 1"},
{"label": "Tab 2", "content": "Content 2"}
],
default_index=0
)
| Property | Type | Description |
|---|---|---|
tabs
|
list |
List of dicts with
label
and
content
|
default_index
|
int | Index of the initially selected tab |
tabs = Tabs(
tabs=[
{"label": "Overview", "content": "Main content"},
{"label": "Details", "content": "Detailed info"}
],
default_index=0
)
The
Table
component displays tabular data with rows and columns.
from dars.components.advanced.table import Table
table = Table(
columns=["Name", "Age", "Country"],
data=[
["Alice", 30, "USA"],
["Bob", 25, "UK"]
]
)
| Property | Type | Description |
|---|---|---|
columns
|
list | List of column headers |
data
|
list | List of rows (each a list/tuple) |
table = Table(
columns=["Product", "Price"],
data=[
["Book", "$10"],
["Pen", "$2"]
]
)
The
GridLayout
component provides a responsive grid-based layout with customizable rows, columns, gaps, and anchor points for precise positioning of children.
from dars.components.layout.grid import GridLayout
from dars.components.basic.text import Text
grid = GridLayout(
rows=2,
cols=2,
gap="24px",
children=[
Text("Top Left"),
Text("Top Right"),
Text("Bottom Left"),
Text("Bottom Right")
]
)
| Property | Type | Description |
|---|---|---|
rows
|
int | Number of grid rows |
cols
|
int | Number of grid columns |
gap
|
str | Gap between grid cells (e.g., "16px") |
children
|
list | List of child components |
anchors
|
dict | Optional anchor points for child placement |
grid = GridLayout(
rows=3,
cols=2,
gap="16px",
children=[Text(f"Cell {i}") for i in range(6)]
)
The
FlexLayout
component provides a responsive flexbox layout, supporting direction, wrap, alignment, and gap between children. Useful for row/column layouts.
from dars.components.layout.flex import FlexLayout
from dars.components.basic.button import Button
flex = FlexLayout(
direction="row",
justify="space-between",
align="center",
gap="12px",
children=[Button("A"), Button("B"), Button("C")]
)
| Property | Type | Description |
|---|---|---|
direction
|
str | Flex direction: "row" or "column" |
wrap
|
str | Flex wrap: "wrap" or "nowrap" |
justify
|
str | Justify content: e.g., "flex-start", "center" |
align
|
str | Align items: e.g., "stretch", "center" |
gap
|
str | Gap between children (e.g., "16px") |
children
|
list | List of child components |
anchors
|
dict | Optional anchor points for child placement |
flex = FlexLayout(
direction="column",
gap="24px",
children=[Button("Save"), Button("Cancel")]
)
The
LayoutBase
component is the base class for all layout components. It allows adding children and anchor/positioning info. You typically use
FlexLayout
or
GridLayout
directly.
from dars.components.layout.grid import LayoutBase
from dars.components.basic.text import Text
layout = LayoutBase(
children=[Text("Item 1"), Text("Item 2")],
anchors={}
)
| Property | Type | Description |
|---|---|---|
children
|
list | List of child components |
anchors
|
dict | Anchor/positioning information |
The
AnchorPoint
class represents an anchor or alignment point for a child in a layout (e.g., top, left, right, bottom, center, percent, or px).
from dars.components.layout.anchor import AnchorPoint
anchor = AnchorPoint(x="left", y="top", name="top-left")
| Property | Type | Description |
|---|---|---|
x
|
str | Horizontal alignment (e.g., "left", "center") |
y
|
str | Vertical alignment (e.g., "top", "center") |
name
|
str | Optional semantic name for the anchor |
anchor = AnchorPoint(x="50%", y="50%", name="center")
The
Card
component is a styled container to group related content, such as a title and other components.
from dars.components.basic.card import Card
from dars.components.basic.text import Text
from dars.components.basic.button import Button
my_card = Card(
title="Título de la Tarjeta",
children=[
Text("Este es el contenido de la tarjeta."),
Button("Ver más")
],
class_name="product-card",
style={
"background-color": "#ffffff",
"border": "1px solid #e0e0e0",
"border-radius": "10px",
"padding": "20px",
"box-shadow": "0 4px 8px rgba(0,0,0,0.05)"
}
)
| Property | Type | Description |
|---|---|---|
title
|
str | Card title |
children
|
list | List of child components |
my_card = Card(
title="Título de la Tarjeta",
children=[
Text("Este es el contenido de la tarjeta."),
Button("Ver más")
],
class_name="product-card",
style={
"background-color": "#ffffff",
"border": "1px solid #e0e0e0",
"border-radius": "10px",
"padding": "20px",
"box-shadow": "0 4px 8px rgba(0,0,0,0.05)"
}
)
The
Modal
component creates an overlay window that appears on top of the main page content.
New in 1.0.5: Modal is now exported as hidden by default (
hiddenattribute anddars-modal-hiddenclass) ifis_open=False, preventing any visual flicker on page load, even if CSS/JS loads slowly.
from dars.components.advanced.modal import Modal
from dars.components.basic.text import Text
from dars.components.basic.button import Button
my_modal = Modal(
title="Welcome to the Modal",
is_open=False, # Now hidden from the very first render
children=[
Text("This is your modal content."),
Button("Close")
],
class_name="welcome-modal",
style={
"background-color": "rgba(0, 0, 0, 0.7)" # Overlay style
}
)
| Property | Type | Description |
|---|---|---|
title
|
str | Modal title |
is_open
|
bool |
Controls modal visibility (
True
to show,
False
to hide). If
False
, modal is hidden from exported HTML.
|
children
|
list | List of child components |
my_modal = Modal(
title="Welcome to the Modal",
is_open=False, # Hidden from the very first render
children=[
Text("This is your modal content."),
Button("Close")
],
class_name="welcome-modal",
style={
"background-color": "rgba(0, 0, 0, 0.7)"
}
)
Note: The exporter now recursively detects advanced components (Tabs, Accordion, Modal, Card) at any nesting level, including inside multipage apps, and applies
minimum_logicrobustly.
The
Navbar
component creates a navigation bar, commonly used at the top of pages.
from dars.components.advanced.navbar import Navbar
from dars.components.basic.link import Link
my_navbar = Navbar(
brand="Mi App",
children=[
Link("Inicio", "/"),
Link("Acerca de", "/about"),
Link("Contacto", "/contact")
],
class_name="main-nav",
style={
"background-color": "#333",
"color": "white",
"padding": "15px 20px"
}
)
| Property | Type | Description |
|---|---|---|
brand
|
str | Texto o componente para la marca/logo de la navegación |
children
|
list |
List of child components (navigation items, usually
Link
s)
|
The
Checkbox
component allows users to select options.
from dars.components.basic.checkbox import Checkbox
mi_checkbox = Checkbox(
label="Acepto términos",
checked=True,
style={
"margin": "10px"
}
)
| Property | Type | Description | Values |
|---|---|---|---|
label
|
str | Texto de la etiqueta |
"Acepto términos"
|
checked
|
bool | Estado de selección |
True
,
False
|
The
RadioButton
component allows users to select one option from a group of options.
from dars.components.basic.radio_button import RadioButton
mi_radio_button = RadioButton(
label="Opción A",
name="grupo1",
checked=False,
style={
"margin": "10px"
}
)
| Property | Type | Description | Values |
|---|---|---|---|
label
|
str | Texto de la etiqueta |
"Opción A"
|
name
|
str | Nombre del grupo de radio buttons |
"grupo1"
|
checked
|
bool | Estado de selección |
True
,
False
|
The
Select
component allows users to select one option from a group of options.
from dars.components.basic.select import Select
mi_select = Select(
options=["Uno", "Dos", "Tres"],
value="Dos",
style={
"width": "200px",
"padding": "10px",
"border": "1px solid #ccc"
}
)
| Property | Type | Description | Values |
|---|---|---|---|
options
|
list | List of options |
["Uno", "Dos", "Tres"]
|
value
|
str | Selected value |
"Dos"
|
The
Slider
component allows users to select a value within a range.
from dars.components.basic.slider import Slider
mi_slider = Slider(
min_value=0,
max_value=100,
value=50,
show_value=True,
style={
"width": "200px",
"padding": "10px"
}
)
| Property | Type | Description | Values |
|---|---|---|---|
min_value
|
int | Minimum value |
0
|
max_value
|
int | Maximum value |
100
|
value
|
int | Valor selectado |
50
|
show_value
|
bool | Mostrar el valor selectado |
True
,
False
|
The
DatePicker
component allows users to select a date.
from dars.components.basic.date_picker import DatePicker
mi_date_picker = DatePicker(
value="2025-08-06",
style={
"width": "200px",
"padding": "10px",
"border": "1px solid #ccc"
}
)
| Property | Type | Description | Values |
|---|---|---|---|
value
|
str | Selected date |
"2025-08-06"
|
Dars supports most standard CSS properties:
width
,
height
min-width
,
min-height
max-width
,
max-height
margin
,
margin-top
,
margin-right
,
margin-bottom
,
margin-left
padding
,
padding-top
,
padding-right
,
padding-bottom
,
padding-left
background-color
color
border-color
font-size
,
font-family
,
font-weight
,
font-style
text-align
,
text-decoration
,
line-height
border
,
border-width
,
border-style
,
border-radius
display
,
position
top
,
right
,
bottom
,
left
,
z-index
flex-direction
,
flex-wrap
justify-content
,
align-items
,
align-content
flex
,
flex-grow
,
flex-shrink
,
flex-basis
grid-template-columns
,
grid-template-rows
grid-gap
,
grid-column
,
grid-row
opacity
,
box-shadow
,
transform
,
transition
# Gradiente de fondo
gradiente = Container(
style={
"background": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
"min-height": "100vh",
"display": "flex",
"align-items": "center",
"justify-content": "center"
}
)
# Animación de hover (para web)
boton_animado = Button(
text="Hover me",
style={
"background-color": "#3498db",
"color": "white",
"padding": "15px 30px",
"border": "none",
"border-radius": "8px",
"transition": "all 0.3s ease",
"transform": "translateY(0)",
"box-shadow": "0 4px 15px rgba(52, 152, 219, 0.3)"
}
)
# Layout de grid
grid_container = Container(
style={
"display": "grid",
"grid-template-columns": "repeat(auto-fit, minmax(250px, 1fr))",
"grid-gap": "20px",
"padding": "20px"
}
)
# Responsive design
responsive_container = Container(
style={
"width": "100%",
"max-width": "1200px",
"margin": "0 auto",
"padding": "0 20px"
}
)
def create_header():
return Container(
children=[
Text("My Application", style={"font-size": "24px", "font-weight": "bold"}),
Text("Descriptive subtitle", style={"color": "#666"})
],
style={
"padding": "20px",
"background-color": "#f8f9fa",
"border-bottom": "1px solid #dee2e6"
}
)
def create_form():
return Container(
children=[
Text("Contact Form", style={"font-size": "20px", "margin-bottom": "20px"}),
Input(placeholder="Name", style={"margin-bottom": "10px"}),
Input(placeholder="Email", input_type="email", style={"margin-bottom": "10px"}),
Button("Send", style={"background-color": "#007bff", "color": "white"})
],
style={
"max-width": "400px",
"margin": "20px auto",
"padding": "20px"
}
)
# Define common styles
BASE_BUTTON_STYLES = {
"padding": "10px 20px",
"border": "none",
"border-radius": "4px",
"font-size": "14px",
"cursor": "pointer"
}
PRIMARY_BUTTON_STYLES = {
**BASE_BUTTON_STYLES,
"background-color": "#007bff",
"color": "white"
}
SECONDARY_BUTTON_STYLES = {
**BASE_BUTTON_STYLES,
"background-color": "#6c757d",
"color": "white"
}
# Use in components
cancel_button = Button("Cancelar", style=SECONDARY_BUTTON_STYLES)
save_button = Button("Guardar", style=PRIMARY_BUTTON_STYLES)
Components provide a solid foundation for creating modern and responsive user interfaces that can be exported to multiple platforms while maintaining consistency and functionality.
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
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.
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"
)
The system supports a comprehensive range of utilities covering layout, spacing, typography, colors, borders, effects, transforms, and more.
You can set
any CSS property
using the
prop-[value]
syntax.
-
). If you prefer, you can also write underscores (
_
) and Dars will convert them to dashes.
[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%);
flex
,
inline-flex
,
grid
,
inline-grid
,
block
,
inline-block
,
inline
,
table
,
table-row
,
table-cell
,
hidden
,
contents
,
flow-root
flex-row
,
flex-row-reverse
,
flex-col
,
flex-col-reverse
flex-wrap
,
flex-wrap-reverse
,
flex-nowrap
justify-start
,
justify-end
,
justify-center
,
justify-between
,
justify-around
,
justify-evenly
justify-items-start
,
justify-items-end
,
justify-items-center
,
justify-items-stretch
items-start
,
items-end
,
items-center
,
items-baseline
,
items-stretch
content-start
,
content-end
,
content-center
,
content-between
,
content-around
,
content-evenly
self-auto
,
self-start
,
self-end
,
self-center
,
self-stretch
,
self-baseline
grow
,
grow-0
,
shrink
,
shrink-0
flex-1
,
flex-auto
,
flex-initial
,
flex-none
order-{n}
basis-{value}
grid-cols-{n}
(e.g.,
grid-cols-3
,
grid-cols-12
)
grid-rows-{n}
col-span-{n}
,
col-start-{n}
,
col-end-{n}
row-span-{n}
,
row-start-{n}
,
row-end-{n}
grid-flow-row
,
grid-flow-col
,
grid-flow-dense
,
grid-flow-row-dense
,
grid-flow-col-dense
auto-cols-auto
,
auto-cols-min
,
auto-cols-max
,
auto-cols-fr
auto-rows-auto
,
auto-rows-min
,
auto-rows-max
,
auto-rows-fr
gap-{n}
,
gap-x-{n}
,
gap-y-{n}
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)
m-{n}
,
mx-{n}
,
my-{n}
,
mt-{n}
,
mr-{n}
,
mb-{n}
,
ml-{n}
,
ms-{n}
,
me-{n}
mx-auto
,
my-auto
0.25rem
units (e.g.,
4
=
1rem
,
8
=
2rem
)
p-[20px]
,
m-[5%]
w-{n}
,
w-full
,
w-screen
,
w-auto
,
w-min
,
w-max
,
w-fit
,
w-1/2
,
w-1/3
,
w-[300px]
h-{n}
,
h-full
,
h-screen
,
h-auto
,
h-min
,
h-max
,
h-fit
,
h-[50vh]
min-w-{n}
,
min-w-full
,
min-w-min
,
min-w-max
,
min-w-fit
min-h-{n}
,
min-h-full
,
min-h-screen
,
min-h-min
,
min-h-max
,
min-h-fit
max-w-{size}
(xs, sm, md, lg, xl, 2xl-7xl, full, min, max, fit, prose, screen-{size})
max-h-{n}
,
max-h-full
,
max-h-screen
,
max-h-min
,
max-h-max
,
max-h-fit
,
max-h-none
size-{n}
(sets both width and height)
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
fs-[32px]
,
fs-[2rem]
Direct font-size specification
ffam-sans
,
ffam-serif
,
ffam-mono
,
ffam-[Open_Sans]
,
ffam-[Times+New+Roman]
font-thin
,
font-extralight
,
font-light
,
font-normal
,
font-medium
,
font-semibold
,
font-bold
,
font-extrabold
,
font-black
italic
,
not-italic
text-left
,
text-center
,
text-right
,
text-justify
,
text-start
,
text-end
text-{color}-{shade}
,
text-[#123456]
underline
,
overline
,
line-through
,
no-underline
uppercase
,
lowercase
,
capitalize
,
normal-case
truncate
,
text-ellipsis
,
text-clip
align-baseline
,
align-top
,
align-middle
,
align-bottom
,
align-text-top
,
align-text-bottom
,
align-sub
,
align-super
whitespace-normal
,
whitespace-nowrap
,
whitespace-pre
,
whitespace-pre-line
,
whitespace-pre-wrap
,
whitespace-break-spaces
break-normal
,
break-words
,
break-all
,
break-keep
leading-none
,
leading-tight
,
leading-snug
,
leading-normal
,
leading-relaxed
,
leading-loose
,
leading-{n}
tracking-tighter
,
tracking-tight
,
tracking-normal
,
tracking-wide
,
tracking-wider
,
tracking-widest
indent-{n}
Complete Palette (50-950 shades for each):
slate
,
gray
,
zinc
,
neutral
,
stone
red
,
rose
orange
,
amber
yellow
,
lime
green
,
emerald
,
teal
cyan
,
sky
,
blue
,
indigo
violet
,
purple
,
fuchsia
pink
black
,
white
,
transparent
,
current
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
bg-{color}-{shade}
,
bg-[#f0f0f0]
bg-[linear-gradient(...)]
(automatically maps to
background-image
)
bg-[radial-gradient(...)]
bg-[url(...)]
bgimg-[...]
(direct
background-image
utility)
bg-bottom
,
bg-center
,
bg-left
,
bg-left-bottom
,
bg-left-top
,
bg-right
,
bg-right-bottom
,
bg-right-top
,
bg-top
bg-repeat
,
bg-no-repeat
,
bg-repeat-x
,
bg-repeat-y
,
bg-repeat-round
,
bg-repeat-space
bg-auto
,
bg-cover
,
bg-contain
bg-fixed
,
bg-local
,
bg-scroll
bg-clip-border
,
bg-clip-padding
,
bg-clip-content
,
bg-clip-text
bg-origin-border
,
bg-origin-padding
,
bg-origin-content
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
border
,
border-0
,
border-2
,
border-4
,
border-8
border-t-{n}
,
border-r-{n}
,
border-b-{n}
,
border-l-{n}
,
border-x-{n}
,
border-y-{n}
border-{color}-{shade}
,
border-[#123456]
border-t-{color}
,
border-r-{color}
,
border-b-{color}
,
border-l-{color}
,
border-x-{color}
,
border-y-{color}
border-solid
,
border-dashed
,
border-dotted
,
border-double
,
border-hidden
,
border-none
rounded
,
rounded-none
,
rounded-sm
,
rounded-md
,
rounded-lg
,
rounded-xl
,
rounded-2xl
,
rounded-3xl
,
rounded-full
,
rounded-[10px]
rounded-t-{size}
,
rounded-r-{size}
,
rounded-b-{size}
,
rounded-l-{size}
,
rounded-tl-{size}
,
rounded-tr-{size}
,
rounded-br-{size}
,
rounded-bl-{size}
shadow-sm
,
shadow
,
shadow-md
,
shadow-lg
,
shadow-xl
,
shadow-2xl
,
shadow-inner
,
shadow-none
opacity-0
,
opacity-25
,
opacity-50
,
opacity-75
,
opacity-100
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
blur-none
,
blur-sm
,
blur
,
blur-md
,
blur-lg
,
blur-xl
,
blur-2xl
,
blur-3xl
brightness-{n}
(0-200)
contrast-{n}
(0-200)
grayscale-{n}
(0-100)
hue-rotate-{n}
(degrees)
invert-{n}
(0-100)
saturate-{n}
(0-200)
sepia-{n}
(0-100)
drop-shadow-sm
,
drop-shadow
,
drop-shadow-md
,
drop-shadow-lg
,
drop-shadow-xl
,
drop-shadow-2xl
,
drop-shadow-none
backdrop-blur-{size}
backdrop-brightness-{n}
backdrop-contrast-{n}
backdrop-grayscale-{n}
backdrop-hue-rotate-{n}
backdrop-invert-{n}
backdrop-opacity-{n}
backdrop-saturate-{n}
backdrop-sepia-{n}
scale-{n}
,
scale-x-{n}
,
scale-y-{n}
(e.g.,
scale-110
= 110%)
rotate-{n}
(degrees)
translate-x-{n}
,
translate-y-{n}
skew-x-{n}
,
skew-y-{n}
(degrees)
origin-center
,
origin-top
,
origin-top-right
,
origin-right
,
origin-bottom-right
,
origin-bottom
,
origin-bottom-left
,
origin-left
,
origin-top-left
transition-none
,
transition-all
,
transition-colors
,
transition-opacity
,
transition-shadow
,
transition-transform
duration-{ms}
(e.g.,
duration-300
,
duration-500
)
delay-{ms}
ease-linear
,
ease-in
,
ease-out
,
ease-in-out
static
,
fixed
,
absolute
,
relative
,
sticky
top-{n}
,
right-{n}
,
bottom-{n}
,
left-{n}
inset-{n}
,
inset-x-{n}
,
inset-y-{n}
z-{n}
(e.g.,
z-10
,
z-50
)
overflow-auto
,
overflow-hidden
,
overflow-clip
,
overflow-visible
,
overflow-scroll
overflow-x-auto
,
overflow-x-hidden
,
overflow-x-clip
,
overflow-x-visible
,
overflow-x-scroll
overflow-y-auto
,
overflow-y-hidden
,
overflow-y-clip
,
overflow-y-visible
,
overflow-y-scroll
visible
,
invisible
,
collapse
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-none
,
pointer-events-auto
select-none
,
select-text
,
select-all
,
select-auto
object-contain
,
object-cover
,
object-fill
,
object-none
,
object-scale-down
object-bottom
,
object-center
,
object-left
,
object-left-bottom
,
object-left-top
,
object-right
,
object-right-bottom
,
object-right-top
,
object-top
aspect-auto
,
aspect-square
,
aspect-video
,
aspect-{w}-{h}
(e.g.,
aspect-16-9
)
float-right
,
float-left
,
float-none
clear-left
,
clear-right
,
clear-both
,
clear-none
box-border
,
box-content
isolate
,
isolation-auto
list-none
,
list-disc
,
list-decimal
list-inside
,
list-outside
appearance-none
,
appearance-auto
resize-none
,
resize-y
,
resize-x
,
resize
scroll-auto
,
scroll-smooth
snap-start
,
snap-end
,
snap-center
,
snap-align-none
snap-normal
,
snap-always
snap-none
,
snap-x
,
snap-y
,
snap-both
,
snap-mandatory
,
snap-proximity
scroll-m-{n}
,
scroll-mx-{n}
,
scroll-my-{n}
,
scroll-mt-{n}
,
scroll-mr-{n}
,
scroll-mb-{n}
,
scroll-ml-{n}
scroll-p-{n}
,
scroll-px-{n}
,
scroll-py-{n}
,
scroll-pt-{n}
,
scroll-pr-{n}
,
scroll-pb-{n}
,
scroll-pl-{n}
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-auto
,
will-change-scroll
,
will-change-contents
,
will-change-transform
columns-{n}
break-after-{value}
break-before-{value}
break-inside-{value}
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)]
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"
)
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()
The parsing happens in Python before the HTML/CSS is generated. This means:
0.25rem
scale (4, 8, 12, 16, etc.) for consistency
transition-all duration-300
for smooth hover effects
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.
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.
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.
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.
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={"padding": "20px"})
@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>
"""
{id}
,
{class_name}
, and
{style}
.
State()
and reactive updates.
on_click
are handled automatically by the framework (passed via
**props
).
{Props.children}
or
{children}
to render nested content.
@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"))
FunctionComponents work seamlessly with all Dars hooks, enabling reactive and interactive behavior.
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"))
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()
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()
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()
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, dScript("console.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 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.
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
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")
)
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)
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)
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)
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)
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")
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")
You can run animations in sequence using the
sequence()
helper or the
.then()
method.
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")
)
.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)
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")
]
Dars Framework 1.4.5 introduces a powerful SPA (Single Page Application) routing system that supports nested routes, layouts, and automatic 404 handling.
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
@routedecorator, you can't add the route of the page inapp.add_page().
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 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")
)
)
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={"display": "flex", "gap": "16px"}
),
Text("Dashboard Footer")
)
)
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
.
The SPA router normalizes paths so that trailing slashes do not create false 404s:
/dashboard
and
/dashboard/
are treated as the same route.
/
remains
/
.
Dars provides robust handling for non-existent routes.
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.
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={"fontSize": "32px"}),
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.
Similar to 404 pages, you can define a custom 403 Forbidden page for unauthorized access to private routes.
Dars includes a default 403 page that informs users they don't have permission to access the requested resource.
You can customize the 403 page using
app.set_403_page()
.
# Create your custom 403 page
forbidden_page = Page(
Container(
Text("⛔ Access Denied", style={"fontSize": "32px", "color": "red"}),
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.
The development server (
dars dev
) includes an intelligent hot reload system for SPAs:
Dars handles SEO automatically in Single Page Applications. The router intelligently updates the document metadata when navigating between routes.
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.
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.
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)
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(
Heading("Welcome!", level=1),
Text("This page is rendered on the server!")
)
app.add_page("home", home(), title="Home")
Dars uses a sophisticated "Dual Hydration" approach:
This prevents Flash of Unstyled Content (FOUC), double rendering, and race conditions.
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
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
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.
SSR in Dars renders your pages on the server before sending them to the client, providing:
/dashboard
)
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
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 ...
)
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
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/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)
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": "/"
}
Dars supports two routing modes that can be mixed in the same application:
RouteType.SSR
)
Server-rendered on every request.
@route("/dashboard", route_type=RouteType.SSR)
def dashboard():
return Page(
Text("Dashboard", level=1),
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
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
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.
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.
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 --backend
# Runs on http://localhost:3000
# Starts uvicorn with the backendEntry from dars.config.json (by default "backend.api:app")
Backend Server (3000) : Renders SSR routes and provides API endpoints.
Communication
: Frontend fetches SSR content from backend via
/api/ssr/*
The
DarsEnv
class automatically configures URLs:
# Development
DarsEnv.get_urls() → {
"backend": "http://localhost:3000",
"frontend": "http://localhost:8000"
}
# Production
DarsEnv.get_urls() → {
"backend": "/",
"frontend": "/"
}
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
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
Example Production Server:
# backend/api.py
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from dars.backend.ssr import create_ssr_app
from main import app as dars_app
app = create_ssr_app(dars_app)
# Serve static files
app.mount("/", StaticFiles(directory="dist", html=True), name="static")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Vercel / Netlify: - Deploy FastAPI backend as serverless function - Serve static files from CDN - Configure environment variables
Dars Framework features powerful state management systems , designed for different use cases:
Modern, Pythonic state management for reactive UIs.
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())
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()
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
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
The
reset()
method restores all properties to their initial values:
state = State(display, text=0, style={"color": "blue"})
# ... user modifies the component ...
# Reset everything back to initial state
reset_btn.on_click = state.reset()
# 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)
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({"color": "red", "fontSize": "24px"})
CSS Classes:
# Set class name
state.class_name.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=dScript("console.log('clicked')"))
Multiple Properties at Once:
state.update(
text="Updated!",
class_name="success",
style={"color": "green"},
on_click=alert("Done!")
)
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)
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
from dars.all import *
app = App("State V2 Demo")
# Timer display
timer_display = Text("0", id="timer", style={"font-size": "36px"})
timer = State(timer_display, text=0)
# Status display
status = Text("Paused", id="status")
status_state = State(status, text="Paused", class_name="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", class_name="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()
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={"color": "red"}))
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={"backgroundColor": "#f0f0f0"},
classes={"add": ["active"], "remove": ["inactive"]}
)
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, dScript
# Using dScript.ARG placeholder for chained values
this().state(text=RawJS(dScript.ARG))
# Using custom JavaScript expressions
this().state(text=RawJS("someVar + ' processed'"))
this()
When:
Dars Framework introduces a Hooks system inspired by React, enabling reactive and stateful behavior in both FunctionComponents and built-in components.
Hooks provide a way to add reactive capabilities to your application. They enable features like:
[!IMPORTANT] When using
Stateobjects with hooks likeuseDynamicanduseValue, 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.
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.
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"))
)
"user-data"
,
"cart-state"
,
"ui-controls"
)
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.
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"))
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
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.
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.
The
useDynamic()
hook creates reactive bindings between external
State
objects and component properties.
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-weight": "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!")
)
)
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.
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>
'''
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.
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.
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!")))
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
Dars provides a set of helpers to make working with DOM values and reactive state completely Pythonic, eliminating the need for raw JavaScript.
The
V()
helper allows you to extract values from
DOM elements
(via CSS selectors) or
reactive state
(via state paths).
from dars.all import *
# Select by ID
V("#myInput")
# Select by Class
V(".myClass")
New in v1.5.8
:
V()
now supports extracting values directly from reactive state created by
useDynamic()
:
# 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
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
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.
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.
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()
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.
V()
supports boolean and comparison operations, enabling declarative validation and conditional logic without raw JavaScript!
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: ==, !=, >, <, >=, <=
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()
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(".")
)
)
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")
)
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())
Pythonic form data collection and submission without raw JavaScript!
The
FormData
class and
collect_form()
helper provide a declarative way to collect form data using
V()
expressions.
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))
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")
})
.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!"
))
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())
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.
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")
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))
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"))
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.
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!
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))
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")
))
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")
))
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")
}))
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())
@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": ""
})
)
)
)
@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"
})
)
)
)
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
# 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")
)
)
# 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"))
))
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!
**Generate client-side timestamps for forms and state updates.
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
# 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")
)
# 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")))
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()
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.
Dars introduces a powerful, declarative system for mathematical expressions using operator overloading. Write complex calculations in pure Python without any inline JavaScript!
The
V()
helper supports:
-
Operator overloading
- Use Python operators (
+
,
-
,
*
,
/
,
%
,
**
)
-
Dynamic operators
- Operators from Select/Input elements
-
Automatic precedence
- Parentheses handled automatically
-
NaN validation
- Safe handling of empty/invalid inputs
-
Type safety
- Numeric operations require
.float()
or
.int()
String concatenation works without any transformations:
from dars.all import *
# Simple concatenation
state.fullname.set(V(".first") + " " + V(".last"))
# With literals
state.message.set("Hello, " + V(".username") + "!")
# Multiple values
state.info.set(V(".name") + " - " + V(".email") + " - " + V(".phone"))
IMPORTANT
: Arithmetic operations
require
.float()
or
.int()
transformations:
# Addition
state.total.set(V(".price").float() + V(".tax").float())
# Subtraction
state.change.set(V(".paid").float() - V(".total").float())
# Multiplication
state.total.set(V(".price").float() * V(".quantity").int())
# Division
state.average.set(V(".sum").float() / V(".count").int())
# Module
state.remainder.set(V(".number").int() % V(".divisor").int())
# Power
state.result.set(V(".base").float() ** V(".exponent").int())
Why transformations are required:
- Prevents accidental string concatenation (e.g.,
"5" * "3"
=
"555"
in JavaScript)
- Ensures type safety
- Makes intent explicit
Dars automatically handles operator precedence following standard mathematical rules:
# a + b * c → a + (b * c) ✅ Automatic
state.result.set(
V(".a").float() + V(".b").float() * V(".c").float()
)
# (a + b) * c → (a + b) * c ✅ Preserved
state.result.set(
(V(".a").float() + V(".b").float()) * V(".c").float()
)
# a ** b ** c → a ** (b ** c) ✅ Right-associative
state.result.set(
V(".a").float() ** V(".b").float() ** V(".c").float()
)
Precedence Table:
| Operator | Precedence | Associativity |
|---|---|---|
**
|
3 (highest) | Right |
*
,
/
,
%
|
2 | Left |
+
,
-
|
1 (lowest) | Left |
You can nest expressions infinitely:
# Complex calculation
state.result.set(
((V(".a").float() + V(".b").float()) * V(".c").float()) /
(V(".d").float() - V(".e").float())
)
# With mixed literals
state.total.set(
(V(".price").float() * V(".qty").int()) * 1.15 # Add 15% tax
)
# Combining multiple operations
state.score.set(
(V(".math").int() + V(".science").int() + V(".english").int()) / 3
)
Use operators from Select or Input elements!
.operator()
Method
Mark a
ValueRef
as a dynamic operator using
.operator()
:
# Select with operator options
Select(
class_name="operation",
options=[
SelectOption("+", "Add"),
SelectOption("-", "Subtract"),
SelectOption("*", "Multiply"),
SelectOption("/", "Divide")
]
)
# Use in calculation
Button(
"Calculate",
on_click=calc.result.set(
V(".num1").float() + V(".operation").operator() + V(".num2").float()
)
)
V(".operation").operator()
extracts the operator value
+
,
-
,
*
,
/
,
%
,
**
)
+
if operator is invalid
from dars.all import *
app = App("Calculator")
calc = State("calc", operation="+", result=0)
@route("/")
def index():
return Page(
# Operation selector
Select(
value=useValue("calc.operation", selector=".operation"),
class_name="operation",
options=[
SelectOption("+", "➕ Add"),
SelectOption("-", "➖ Subtract"),
SelectOption("*", "✖️ Multiply"),
SelectOption("/", "➗ Divide")
]
),
# Number inputs
Input(class_name="num1", input_type="number", placeholder="First number"),
Input(class_name="num2", input_type="number", placeholder="Second number"),
# Calculate button - DECLARATIVE!
Button(
"Calculate",
on_click=calc.result.set(
V(".num1").float() + V(".operation").operator() + V(".num2").float()
)
),
# Result display
Text(text=useDynamic("calc.result"))
)
app.add_page("index", index())
if __name__ == "__main__":
app.rTimeCompile()
The
.operator()
method validates against this whitelist:
+
- Addition
-
- Subtraction
*
- Multiplication
/
- Division
%
- Modulo
**
- Power
Invalid operators
automatically fall back to
+
with a console warning.
New in v1.6.4 : Automatic NaN handling prevents errors from empty or invalid inputs.
All mathematical expressions include built-in NaN validation:
# If inputs are empty or invalid
Button(
"Calculate",
on_click=calc.result.set(
V(".num1").float() + V(".num2").float()
)
)
# Empty inputs → Returns 0
# Console: "[Dars] Invalid input: one or more values are NaN. Returning 0."
# Empty inputs
V(".empty-input").float() # → 0 (with warning)
# Invalid division
V(".num").float() / 0 # → 0 (with warning)
# Invalid operation
V(".text").float() # → 0 (with warning)
MathExpression
objects work seamlessly with
State.set()
:
# Simple expression
calc.result.set(V(".a").float() + V(".b").float())
# Complex expression
calc.total.set(
(V(".price").float() * V(".qty").int()) * 1.15
)
# With dynamic operator
calc.result.set(
V(".num1").float() + V(".op").operator() + V(".num2").float()
)
All expressions are automatically wrapped in async IIFEs:
# Your code
calc.result.set(V(".a").float() + V(".b").float())
# Generated JavaScript
(async () => {
const left = await (/* V(".a").float() */);
const right = await (/* V(".b").float() */);
if (isNaN(left) || isNaN(right)) {
console.warn('[Dars] Invalid input: one or more values are NaN. Returning 0.');
return 0;
}
const result = left + right;
if (isNaN(result)) {
console.warn('[Dars] Operation resulted in NaN. Returning 0.');
return 0;
}
return result;
})()
# Calculate total with tax and discount
Button(
"Calculate Total",
on_click=cart.total.set(
((V(".price").float() * V(".qty").int()) * 1.15) - V(".discount").float()
)
)
# Different calculations based on operator
Select(class_name="calc-type", options=[
SelectOption("area", "Area"),
SelectOption("perimeter", "Perimeter")
])
# Area: length * width
# Perimeter: 2 * (length + width)
Button(
"Calculate",
on_click=dScript(f"""
const type = {V(".calc-type").get_code()};
const length = {V(".length").float().get_code()};
const width = {V(".width").float().get_code()};
let result;
if (type === 'area') {{
result = length * width;
}} else {{
result = 2 * (length + width);
}}
window.Dars.change({{
id: 'calc',
dynamic: true,
result: result
}});
""")
)
# Power calculation
calc.result.set(V(".base").float() ** V(".exponent").float())
# Modulo for remainders
calc.remainder.set(V(".dividend").int() % V(".divisor").int())
# Complex formula: (a² + b²)^0.5 (Pythagorean theorem)
calc.hypotenuse.set(
(V(".a").float() ** 2 + V(".b").float() ** 2) ** 0.5
)
Always use transformations for math
V(".price").float() * V(".qty").int() # ✅ Correct
Use descriptive class names
Input(class_name="price-input") # ✅ Clear
V(".price-input").float()
Leverage automatic precedence
V(".a").float() + V(".b").float() * V(".c").float() # ✅ Auto-parens
Use dynamic operators for flexibility
V(".num1").float() + V(".op").operator() + V(".num2").float() # ✅ Dynamic
Don't use arithmetic without transformations
V(".price") * V(".qty") # ❌ TypeError
Don't rely on implicit type coercion
V(".number") + 5 # ❌ String concatenation, not addition
Don't forget NaN is handled automatically
# No need for manual checks
if (isNaN(V(".num").float())) { ... }
# Automatic validation
calc.result.set(V(".num").float() + 10)
Created by
V(selector)
:
class ValueRef:
def int() -> ValueRef # Convert to integer
def float() -> ValueRef # Convert to float
def upper() -> ValueRef # Convert to uppercase
def lower() -> ValueRef # Convert to lowercase
def trim() -> ValueRef # Remove whitespace
def operator() -> DynamicOperator # Mark as dynamic operator
# Operators
def __add__(other) -> MathExpression
def __sub__(other) -> MathExpression
def __mul__(other) -> MathExpression
def __truediv__(other) -> MathExpression
def __mod__(other) -> MathExpression
def __pow__(other) -> MathExpression
Created by operator overloading:
class MathExpression:
# Supports all arithmetic operators
# Automatically handles precedence
# Validates NaN
# Generates async JavaScript
Created by
.operator()
:
class DynamicOperator:
VALID_OPERATORS = ['+', '-', '*', '/', '%', '**']
# Validates operator at runtime
# Falls back to '+' if invalid
Dars Framework provides a powerful,
Pythonic system
for handling HTTP requests and API communication without writing any JavaScript. The
dars.backend
module enables you to fetch data, bind it to components, and create reactive UIs entirely in Python.
from dars.all import *
from dars.backend import get, useData
app = App(title="API Demo")
# Create display component
user_display = Text("No data", id="user-name")
user_state = State(user_display, text="No data")
# Fetch and bind data - pure Python!
fetch_btn = Button(
"Fetch User",
on_click=get(
id="userData",
url="https://api.example.com/user/1",
callback=user_state.text.set(useData('userData').name)
)
)
app.set_root(Container(user_display, fetch_btn))
if __name__ == "__main__":
app.rTimeCompile()
The
dars.backend
module provides standard HTTP methods that return
dScript
objects:
get(id, url, **options)
Performs a GET request.
from dars.backend import get
# Basic GET
get_user = get(
id="userData",
url="https://jsonplaceholder.typicode.com/users/1"
)
# With callback
get_user = get(
id="userData",
url="https://api.example.com/user/1",
callback=status_state.text.set("✅ Loaded!"),
on_error=status_state.text.set("❌ Error!")
)
post(id, url, body, **options)
Performs a POST request.
from dars.backend import post
# POST with JSON body
create_user = post(
id="createResult",
url="https://api.example.com/users",
body={"name": "John", "email": "john@example.com"},
callback=status_state.text.set("User created!")
)
put(id, url, body, **options)
- Update resource
delete(id, url, **options)
- Delete resource
patch(id, url, body, **options)
- Partial update
fetch(id, url, method, **options)
- Generic fetch
All HTTP functions accept these options:
| Option | Type | Description |
|---|---|---|
id
|
str
|
Required . Operation ID (NOT HTML ID) for accessing data |
url
|
str
|
Required . API endpoint URL |
headers
|
dict
|
Custom HTTP headers |
callback
|
dScript
|
Executed on success |
on_error
|
dScript
|
Executed on error |
parse_json
|
bool
|
Auto-parse JSON response (default:
True
)
|
timeout
|
int
|
Request timeout in milliseconds |
The
useData()
function provides
Pythonic access
to fetched data using dot notation:
from dars.backend import useData
# Access fetched data by operation ID
user_data = useData('userData')
# Access nested properties with dot notation
user_name = useData('userData').name
user_email = useData('userData').email
user_address_city = useData('userData').address.city
get(id="userData", ...)
, the response is stored in
window.userData
useData('userData')
creates a
DataAccessor
object
.name
uses
__getattr__
to create
window.userData?.name
.code
property generates the JavaScript expression
The most powerful feature is binding API data directly to component states:
from dars.all import *
from dars.backend import get, useData
# Create components and states
name_display = Text("", id="user-name")
email_display = Text("", id="user-email")
name_state = State(name_display, text="")
email_state = State(email_display, text="")
# Fetch and bind - pure Python!
fetch_button = Button(
"Fetch User",
on_click=get(
id="userData",
url="https://jsonplaceholder.typicode.com/users/1",
# Chain multiple state updates with .then()
callback=(
name_state.text.set(useData('userData').name)
.then(email_state.text.set(useData('userData').email))
)
)
)
.then()
Chain multiple operations sequentially:
# Update multiple components
callback=(
status_state.text.set("Loading...")
.then(name_state.text.set(useData('userData').name))
.then(email_state.text.set(useData('userData').email))
.then(status_state.text.set("✅ Loaded!"))
)
Helper functions for working with JSON data:
stringify(data, pretty=False)
Convert data to JSON string:
from dars.backend import stringify, useData
# Stringify fetched data
display_state.text.set(stringify(useData('userData'), pretty=True))
# Stringify Python objects
json_str = stringify({"name": "John", "age": 30})
parse(json_string)
Parse JSON string:
from dars.backend import parse
# Parse JSON string
data = parse('{"name": "John"}')
get_value(obj, path, default=None)
Safely access nested values:
from dars.backend import get_value, useData
# Safe nested access with default
city = get_value(useData('userData'), 'address.city', default='Unknown')
Create, update, and delete components dynamically at runtime:
createComp(target, root, position="append")
Create a new component in the DOM:
from dars.backend import createComp
# Create new component
new_text = Text("Hello!", id="new-item")
create_btn.on_click = createComp(
target=new_text,
root="container-id",
position="append" # or "prepend", "before:id", "after:id"
)
Tip:
You can create a
State()
for a component before it exists using a string ID:
# Create state with string ID
item_state = State("new-item", text="Hello!")
# Create component later
create_btn.on_click = createComp(
target=Text("Hello!", id="new-item"),
root="container-id"
)
# State works immediately!
update_btn.on_click = item_state.text.set("Updated!")
updateComp(target, **props)
Update component properties:
from dars.backend import updateComp
# Update component
update_btn.on_click = updateComp(
"my-component-id",
text="Updated!",
style={"color": "red"}
)
deleteComp(id)
Remove a component from the DOM:
from dars.backend import deleteComp
# Delete component
delete_btn.on_click = deleteComp("component-id")
id
to avoid conflicts
.then()
to chain multiple state updates sequentially
on_error
callbacks for better UX
get(id, url, **options)
- GET request
post(id, url, body, **options)
- POST request
put(id, url, body, **options)
- PUT request
delete(id, url, **options)
- DELETE request
patch(id, url, body, **options)
- PATCH request
fetch(id, url, method, **options)
- Generic fetch
useData(operation_id)
- Access fetched data with dot notation
stringify(data, pretty=False)
- Convert to JSON string
parse(json_string)
- Parse JSON
get_value(obj, path, default=None)
- Safe nested access
createComp(target, root, position)
- Create component
updateComp(target, **props)
- Update component
deleteComp(id)
- Delete component
For more examples, see the test files in
tst/proj/test_http_demo.py
and
tst/proj/test_http_utils.py
.
Dars provides a built-in
create_ssr_app
helper to easily serve your Dars application with Server-Side Rendering (SSR) using FastAPI.
When you run
dars init --type ssr
, Dars creates a backend structure for you:
*
backend/api.py
: Entry point for the FastAPI server (Default Port: 8000).
*
backend/apiConfig.py
: Configuration helper for environment management.
apiConfig.py
)
To switch between Development and Production modes, simply edit the
MODE
variable in
backend/apiConfig.py
:
class DarsEnv:
# Set this to "production" when deploying
MODE = "development"
DEV = "development"
BUILD = "production"
# ...
localhost:8000
, Frontend on
localhost:3000
.
dars dev
proxies requests.
# Start the SSR Backend (Port 3000)
python backend/api.py
In a separate terminal, run the frontend dev server:
# Start Frontend Dev Server (Port 8000)
dars dev
This is the documentation for the events in Dars.
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, dScript("console.log('click')"))
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"
on_*
attribute can now accept:
Example using
Mod.set
:
Mod.set("btn1", on_click=[st1.state(0), dScript(code="console.log('clicked')")])
Runtime behavior:
Mod.set
replaces the previous one.
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!"))
)
)
]
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.
All Dars components support the
on_key_press
event handler for keyboard interactions.
from dars.all import *
Input(
id="search",
on_key_press=log("Key pressed!")
)
Note: Use
on_key_pressas the universal keyboard event. The olderon_key_downandon_key_upevents have been deprecated in favor of this simpler approach.
The
KeyCode
class provides constants for all keyboard keys, making your code more readable and maintainable.
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
# Get key code by name
key = KeyCode.key('enter') # Returns 'Enter'
key = KeyCode.key('A') # Returns 'a'
The
onKey()
function is the
recommended way
to handle specific keyboard keys with optional modifier keys.
from dars.all import *
# Simple key detection
Input(
on_key_press=onKey(KeyCode.ENTER, log("Enter pressed!"))
)
# 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)
)
ctrl
- Ctrl key (Command on Mac)
shift
- Shift key
alt
- Alt key
meta
- Meta/Command key
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)
Use
switch()
to handle multiple different keys in a single event handler.
from dars.all import *
Input(
on_key_press=switch({
KeyCode.ENTER: log("Enter pressed"),
KeyCode.ESCAPE: alert("Escape pressed"),
})
)
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!")
]
})
)
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()
Use
addGlobalKeys()
to create app-wide keyboard shortcuts that work anywhere on the page.
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.
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()
})
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")
]
})
addGlobalKeys(app, {
(KeyCode.Z, 'ctrl'): undo(),
(KeyCode.Z, 'ctrl', 'shift'): redo(),
(KeyCode.S, 'ctrl', 'shift'): save_as()
})
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={"font-size": "24px", "font-weight": "bold"}),
Text(useDynamic("document.message"), style={"color": "#666"}),
Input(
id="editor",
placeholder="Start typing...",
style={"width": "100%", "min-height": "200px"}
),
Container(
style={"margin-top": "20px", "padding": "15px", "background": "#f5f5f5"},
children=[
Text("Global Shortcuts:", style={"font-weight": "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()
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
})
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")
})
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()
})
)
formState = State("form", username="")
Input(
id="username",
on_key_press=onKey(KeyCode.ENTER, formState.username.set(V("#username")))
)
on_key_press
for all keyboard events (replaces
on_key_down
and
on_key_up
)
KeyCode
constants for readable key references
onKey()
for single key detection with optional modifiers
switch()
for handling multiple different keys
addGlobalKeys()
for app-wide shortcuts (always with modifiers!)
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.
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
The HTML exporter generates standard web applications that can run in any browser.
dars export my_app.py --format html --output ./dist
dist/
├── index.html # Main page
├── styles.css # CSS styles
└── script.js # JavaScript logic
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;
}
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"
The desktop exporter allows you to package your Dars app as a native desktop application. This feature is currently in BETA: usable for testing and internal tooling, but not recommended for production deployments yet.
dars dev
with desktop apps is fully supported, but for now it closes and reopen the electron dev app when a file change is detected.
Initialize or update your project for desktop:
dars init --type desktop
# or if you already have a project
dars init --update
Ensure your project config has desktop format:
{
"entry": "main.py",
"format": "desktop",
"outdir": "dist",
"targetPlatform": "auto"
}
Verify optional tooling (Node/Bun, Electron, electron‑builder):
dars doctor --all --yes
Build your desktop app:
dars build
Artifacts will be placed in
dist/
. The desktop source (used for packaging) is emitted to
dist/source-electron/
.
You can access native filesystem functions via the
dars.desktop
module:
from dars.desktop import read_text, write_text, read_file, write_file, list_directory, get_value
from dars.core.state import this
from dars.scripts.dscript import RawJS, dScript
The desktop module provides async file operations that return
dScript
objects, perfect for chaining with
this().state()
:
Reading Text Files
# Simple read - button updates itself with file content
read_btn = Button("Load Config",
on_click=read_text("config.txt").then(
this().state(text=RawJS(dScript.ARG))
)
)
# Update another component
display = Text("", id="display")
load_btn = Button("Load Data",
on_click=read_text("data.txt").then(
update_component("display", text=RawJS(dScript.ARG))
)
)
Writing Text Files
save_btn = Button("Save",
on_click=write_text("output.txt", "Hello Dars!").then(
this().state(text="Saved!", style={"color": "green"})
)
)
Listing Directories
from dars.desktop import list_directory, get_value
Button("Browse",
on_click=list_directory(".").then(
this().state(id="file-list", html=RawJS("""
value.map(f => {
const icon = f.isDirectory ? '📁' : '📄';
return `<div>${icon} ${f.name}</div>`;
}).join('')
"""))
)
)
Button("Python Files",
on_click=list_directory(".", "*.py").then(
this().state(id="count", text=RawJS("`Found ${value.length} files`"))
)
)
Input(id="path", value=".")
Button("List Directory",
on_click=list_directory(get_value("path")).then(
this().state(id="output", html=RawJS("value.map(f => f.name).join('<br>')"))
)
)
list_directory(".", "*", include_size=True)
Binary File Operations
img_data = read_file("image.png")
write_file("output.bin", data_bytes)
Use
dScript.then()
for sequential operations:
# Read → Process → Update → Write
Button("Process File",
on_click=read_text("input.txt")
.then(dScript(code="const processed = value.toUpperCase(); return processed;"))
.then(write_text("output.txt", RawJS("processed")))
.then(this().state(text="Complete!"))
)
All file paths are relative to the app's directory. See
State Management
and
Scripts
for more details on
this()
and chaining.
targetPlatform
:
auto
|
windows
|
linux
|
macos
.
App
instance when available: title, description, author, version.
0.1.0
is used and a warning is shown.
The script system of Dars allows adding interactive logic and dynamic behaviors to applications. Scripts are written in JavaScript and seamlessly integrate with UI components.
Scripts in Dars are fragments of JavaScript code that:
Dars supports three main types of scripts:
All scripts inherit from the base
Script
class:
from abc import ABC, abstractmethod
class Script(ABC):
def __init__(self):
pass
@abstractmethod
def get_code(self) -> str:
"""Retorna el código del script"""
pass
dScript is a flexible class that allows you to define a script as either:
- Inline JavaScript (via the
code
argument)
- Or as a reference to an external file (via the
file_path
argument)
But never both at the same time . This is useful for presets, user-editable actions, and advanced integrations.
from dars.scripts.dscript import dScript
# Inline JS
script_inline = dScript(code="""
function hello() { alert('Hello from dScript!'); }
document.addEventListener('DOMContentLoaded', hello);
""")
# External file
script_file = dScript(file_path="./scripts/my_script.js")
from dars.scripts.dscript import dScript
custom_action = dScript(code="""
function customClick() {
alert('Custom action from preset!');
}
document.addEventListener('DOMContentLoaded', function() {
var btn = document.getElementById('my-btn');
if (btn) btn.onclick = customClick;
});
""")
app.add_script(custom_action)
.then()
)
You can chain multiple
dScript
objects using the
.then()
method. This is particularly useful when working with asynchronous operations like file reading in Electron.
from dars.desktop import read_text
from dars.core.state import this
from dars.scripts.dscript import RawJS, dScript
# Read a file and update a component with its content
# The result of the previous script is available as dScript.ARG (which resolves to 'value')
read_op = read_text("data.txt")
update_op = this().state(text=RawJS(dScript.ARG))
chained_script = read_op.then(update_op)
The
RawJS
wrapper ensures that
dScript.ARG
is treated as a variable name (
value
) rather than a string literal
"value"
.
state.state()
The
state.state(idx)
method allows you to navigate a component to a specific state index. This is the recommended way to trigger state transitions.
Requirements
:
- The component must have a
dState
defined with the target index in its
states
array
- The index must be valid (0 to states.length - 1)
Example: Toggle Button
from dars.all import *
from dars.core.state import dState
# Create a button that toggles between two states
toggle_btn = Button("Off", id="ToggleBtn")
# Define states for the button
toggle_state = dState("toggle", component=toggle_btn, states=[0, 1])
# Configure state 1 appearance and behavior
toggle_state.cState(1, mods=[
Mod.set(toggle_btn,
text="On",
style={'background-color': 'green', 'color': 'white'},
on_click=toggle_state.state(0) # Return to state 0 when clicked
)
])
# Initial click navigates to state 1
toggle_btn.on_click = toggle_state.state(1)
Example: Cycle Through States
# Create a button that cycles through multiple states
cycle_btn = Button("State 0", id="CycleBtn")
cycle_state = dState("cycler", component=cycle_btn, states=[0, 1, 2, 3])
# Each state navigates to the next
cycle_state.cState(1, mods=[
Mod.set(cycle_btn, text="State 1", on_click=cycle_state.state(2))
])
cycle_state.cState(2, mods=[
Mod.set(cycle_btn, text="State 2", on_click=cycle_state.state(3))
])
cycle_state.cState(3, mods=[
Mod.set(cycle_btn, text="State 3", on_click=cycle_state.state(0))
])
cycle_btn.on_click = cycle_state.state(1) # Start the cycle
from dars.scripts.script import InlineScript
script = InlineScript("""
function saludar() {
alert('¡Hola desde Dars!');
}
document.addEventListener('DOMContentLoaded', function() {
console.log('Aplicación cargada');
});
""")
The exporter (
html_css_js.py
) automatically detects and exports all scripts of type
dScript
,
InlineScript
, and
FileScript
. You can safely mix and match them in your app, and all will be included in the generated JS.
Mod.set(..., on_*=...)
) are serialized to a JSON-safe form as
{ "code": "..." }
and reconstituted at runtime.
on_*
) accept a single script or an array of scripts (any mix of InlineScript, FileScript, dScript, or raw JS strings). The runtime runs them sequentially and guarantees a single active dynamic listener per event.
from dars.scripts.script import FileScript
# Load script from file
script = FileScript("./scripts/mi_script.js")
utils_ds
)
Dars provides a collection of utility functions in
dars.scripts.utils_ds
(exported in
dars.all
) that return pre-configured
dScript
objects for common tasks. These allow you to implement interactivity without writing raw JavaScript.
goTo(href)
: Navigate to a URL in the current tab.
goToNew(href)
: Open a URL in a new tab.
reload()
: Reload the current page.
goBack()
: Navigate back in browser history.
goForward()
: Navigate forward in browser history.
Button("Home", on_click=goTo("/"))
Button("Docs", on_click=goToNew("https://docs.dars.dev"))
show(id)
: Show an element (display: block).
hide(id)
: Hide an element (display: none).
toggle(id)
: Toggle visibility.
setText(id, text)
: Set text content.
addClass(id, class_name)
: Add a CSS class.
removeClass(id, class_name)
: Remove a CSS class.
toggleClass(id, class_name)
: Toggle a CSS class.
Button("Show Details", on_click=show("details-panel"))
Button("Toggle Theme", on_click=toggleClass("app-root", "dark-mode"))
setTimeout(delay, code)
: Set a timeout to execute a script after a delay.
Button("Delayed Action", on_click=setTimeout(delay=2000, code="alert('Delayed!')"))
showModal(id)
: Show a Dars Modal component (handles hidden attribute and class).
hideModal(id)
: Hide a Dars Modal component.
Button("Open Modal", on_click=showModal("my-modal"))
submitForm(form_id)
: Submit a form.
resetForm(form_id)
: Reset a form.
getValue(input_id, target_id)
: Copy value from input to another element's text.
clearInput(input_id)
: Clear an input field.
Button("Submit", on_click=submitForm("contact-form"))
Button("Clear", on_click=clearInput("search-box"))
saveToLocal(key, value)
: Save string value.
loadFromLocal(key, target_id)
: Load value and set as text of target element.
removeFromLocal(key)
: Remove item.
clearLocalStorage()
: Clear all storage.
Button("Save Prefs", on_click=saveToLocal("theme", "dark"))
copyToClipboard(text)
: Copy text string.
copyElementText(id)
: Copy text content of an element.
Button("Copy Code", on_click=copyElementText("code-block"))
scrollTo(x, y)
: Scroll to position.
scrollToTop()
: Smooth scroll to top.
scrollToBottom()
: Smooth scroll to bottom.
scrollToElement(id)
: Smooth scroll to specific element.
Button("Back to Top", on_click=scrollToTop())
alert(message)
: Show browser alert.
confirm(message, on_ok, on_cancel)
: Show confirm dialog.
read_op = read_text("data.txt")
update_op = this().state(text=RawJS(dScript.ARG))
chained_script = read_op.then(update_op)
The
RawJS
wrapper ensures that
dScript.ARG
is treated as a variable name (
value
) rather than a string literal
"value"
.
state.state()
The
state.state(idx)
method allows you to navigate a component to a specific state index. This is the recommended way to trigger state transitions.
Requirements
:
- The component must have a
dState
defined with the target index in its
states
array
- The index must be valid (0 to states.length - 1)
Example: Toggle Button
from dars.all import *
from dars.core.state import dState
# Create a button that toggles between two states
toggle_btn = Button("Off", id="ToggleBtn")
# Define states for the button
toggle_state = dState("toggle", component=toggle_btn, states=[0, 1])
# Configure state 1 appearance and behavior
toggle_state.cState(1, mods=[
Mod.set(toggle_btn,
text="On",
style={'background-color': 'green', 'color': 'white'},
on_click=toggle_state.state(0) # Return to state 0 when clicked
)
])
# Initial click navigates to state 1
toggle_btn.on_click = toggle_state.state(1)
Example: Cycle Through States
# Create a button that cycles through multiple states
cycle_btn = Button("State 0", id="CycleBtn")
cycle_state = dState("cycler", component=cycle_btn, states=[0, 1, 2, 3])
# Each state navigates to the next
cycle_state.cState(1, mods=[
Mod.set(cycle_btn, text="State 1", on_click=cycle_state.state(2))
])
cycle_state.cState(2, mods=[
Mod.set(cycle_btn, text="State 2", on_click=cycle_state.state(3))
])
cycle_state.cState(3, mods=[
Mod.set(cycle_btn, text="State 3", on_click=cycle_state.state(0))
])
cycle_btn.on_click = cycle_state.state(1) # Start the cycle
from dars.scripts.script import InlineScript
script = InlineScript("""
function saludar() {
alert('¡Hola desde Dars!');
}
document.addEventListener('DOMContentLoaded', function() {
console.log('Aplicación cargada');
});
""")
The exporter (
html_css_js.py
) automatically detects and exports all scripts of type
dScript
,
InlineScript
, and
FileScript
. You can safely mix and match them in your app, and all will be included in the generated JS.
Mod.set(..., on_*=...)
) are serialized to a JSON-safe form as
{ "code": "..." }
and reconstituted at runtime.
on_*
) accept a single script or an array of scripts (any mix of InlineScript, FileScript, dScript, or raw JS strings). The runtime runs them sequentially and guarantees a single active dynamic listener per event.
from dars.scripts.script import FileScript
# Load script from file
script = FileScript("./scripts/mi_script.js")
utils_ds
)
Dars provides a collection of utility functions in
dars.scripts.utils_ds
(exported in
dars.all
) that return pre-configured
dScript
objects for common tasks. These allow you to implement interactivity without writing raw JavaScript.
goTo(href)
: Navigate to a URL in the current tab.
goToNew(href)
: Open a URL in a new tab.
reload()
: Reload the current page.
goBack()
: Navigate back in browser history.
goForward()
: Navigate forward in browser history.
Button("Home", on_click=goTo("/"))
Button("Docs", on_click=goToNew("https://docs.dars.dev"))
show(id)
: Show an element (display: block).
hide(id)
: Hide an element (display: none).
toggle(id)
: Toggle visibility.
setText(id, text)
: Set text content.
addClass(id, class_name)
: Add a CSS class.
removeClass(id, class_name)
: Remove a CSS class.
toggleClass(id, class_name)
: Toggle a CSS class.
Button("Show Details", on_click=show("details-panel"))
Button("Toggle Theme", on_click=toggleClass("app-root", "dark-mode"))
setTimeout(delay, code)
: Set a timeout to execute a script after a delay.
Button("Delayed Action", on_click=setTimeout(delay=2000, code="alert('Delayed!')"))
showModal(id)
: Show a Dars Modal component (handles hidden attribute and class).
hideModal(id)
: Hide a Dars Modal component.
Button("Open Modal", on_click=showModal("my-modal"))
submitForm(form_id)
: Submit a form.
resetForm(form_id)
: Reset a form.
getValue(input_id, target_id)
: Copy value from input to another element's text.
clearInput(input_id)
: Clear an input field.
Button("Submit", on_click=submitForm("contact-form"))
Button("Clear", on_click=clearInput("search-box"))
saveToLocal(key, value)
: Save string value.
loadFromLocal(key, target_id)
: Load value and set as text of target element.
removeFromLocal(key)
: Remove item.
clearLocalStorage()
: Clear all storage.
Button("Save Prefs", on_click=saveToLocal("theme", "dark"))
copyToClipboard(text)
: Copy text string.
copyElementText(id)
: Copy text content of an element.
Button("Copy Code", on_click=copyElementText("code-block"))
scrollTo(x, y)
: Scroll to position.
scrollToTop()
: Smooth scroll to top.
scrollToBottom()
: Smooth scroll to bottom.
scrollToElement(id)
: Smooth scroll to specific element.
Button("Back to Top", on_click=scrollToTop())
alert(message)
: Show browser alert.
confirm(message, on_ok, on_cancel)
: Show confirm dialog.
log(message)
: Log to console.
focus(id)
: Focus an element.
blur(id)
: Blur an element.
Button("Delete", on_click=confirm(
"Are you sure?",
on_ok="console.log('Deleted')",
on_cancel="console.log('Cancelled')"
))
Dars includes a comprehensive animation system with 15+ built-in animations. All animation functions return
dScript
objects and can be chained for complex sequences.
from dars.all import fadeIn, fadeOut
# Fade in an element
button.on_click = fadeIn(id="box", duration=500, easing="ease")
# Fade out an element
button.on_click = fadeOut(id="box", duration=500, hide_after=True)
Parameters:
-
id
: Element ID to animate
-
duration
: Animation duration in milliseconds (default: 500)
-
easing
: CSS easing function (default: "ease")
-
hide_after
: For fadeOut, set display:none after animation (default: True)
from dars.all import slideIn, slideOut
# Slide in from left
button.on_click = slideIn(id="panel", direction="left", duration=400)
# Slide out to right
button.on_click = slideOut(id="panel", direction="right", duration=400)
Directions:
"left"
,
"right"
,
"top"
,
"bottom"
,
"top-left"
,
"top-right"
,
"bottom-left"
,
"bottom-right"
from dars.all import scaleIn, scaleOut
# Scale in from 0.3 to 1
button.on_click = scaleIn(id="popup", from_scale=0.3, duration=400)
# Scale out to 0.5
button.on_click = scaleOut(id="popup", to_scale=0.5, duration=400)
from dars.all import shake
# Shake effect
button.on_click = shake(id="alert", intensity=10, duration=500)
Parameters:
-
intensity
: Shake distance in pixels (default: 10)
-
duration
: Total animation duration (default: 500)
from dars.all import bounce
# Bounce effect
button.on_click = bounce(id="element", distance=20, duration=600)
from dars.all import pulse
# Pulse effect
button.on_click = pulse(id="button", scale=1.1, duration=400, iterations=2)
Parameters:
-
scale
: Maximum scale factor (default: 1.1)
-
iterations
: Number of pulses (default: 1, use "infinite" for continuous)
from dars.all import rotate
# Rotate 360 degrees
button.on_click = rotate(id="spinner", degrees=360, duration=1000)
from dars.all import flip
# Flip on Y axis
button.on_click = flip(id="card", axis="y", duration=600)
Axis:
"x"
(horizontal),
"y"
(vertical)
from dars.all import colorChange
# Change background color
button.on_click = colorChange(
id="box",
property="background",
from_color="#ff0000",
to_color="#00ff00",
duration=500
)
Properties:
"color"
,
"background"
,
"border-color"
from dars.all import morphSize
# Morph to new size
button.on_click = morphSize(
id="box",
to_width="300px",
to_height="200px",
duration=500
)
Chain multiple animations to run one after another:
from dars.all import sequence, fadeIn, pulse, shake
button.on_click = sequence(
fadeIn(id="box", duration=300),
pulse(id="box", scale=1.2, iterations=2),
shake(id="box", intensity=5)
)
Run animations simultaneously (use
.then()
with same timing):
from dars.all import fadeIn, scaleIn
# Both animations run at the same time
button.on_click = fadeIn(id="box").then(scaleIn(id="other-box"))
Animations integrate seamlessly with State V2:
from dars.all import *
display = Text("0", id="counter")
counter = State(display, text=0)
button.on_click = sequence(
counter.text.increment(by=1),
pulse(id="counter", scale=1.2),
fadeOut(id="counter", duration=200),
counter.text.set(value=0),
fadeIn(id="counter", duration=200)
)
from dars.all import *
app = App("Animation Demo")
# Create animated box
box = Container(
Text("Animate Me!", style={"color": "white"}),
id="anim-box",
style={
"background": "linear-gradient(135deg, #667eea, #764ba2)",
"padding": "40px",
"border-radius": "16px",
"text-align": "center"
}
)
# Animation buttons
fade_btn = Button("Fade In", on_click=fadeIn(id="anim-box", duration=600))
slide_btn = Button("Slide In", on_click=slideIn(id="anim-box", direction="left"))
shake_btn = Button("Shake", on_click=shake(id="anim-box", intensity=10))
pulse_btn = Button("Pulse", on_click=pulse(id="anim-box", scale=1.15, iterations=3))
# Sequence button
sequence_btn = Button("Combo", on_click=sequence(
fadeIn(id="anim-box", duration=400),
pulse(id="anim-box", scale=1.1, iterations=2),
shake(id="anim-box", intensity=5, duration=400)
))
page = Page(Container(box, fade_btn, slide_btn, shake_btn, pulse_btn, sequence_btn))
app.add_page("index", page, index=True)
Use appropriate durations : Most animations work well between 300-600ms
fadeIn(id="box", duration=400) # Good - feels responsive
fadeIn(id="box", duration=2000) # Too slow - users will get impatient
Match animation to context : Use subtle animations for frequent actions
# Frequent action - subtle
button.on_click = pulse(id="counter", scale=1.05, duration=200)
# Important action - more dramatic
success_btn.on_click = sequence(
scaleIn(id="message", from_scale=0.5, duration=400),
pulse(id="message", scale=1.1, iterations=2)
)
Don't overuse sequence : Long animation chains can frustrate users
# Good - 2-3 animations
sequence(fadeIn(id="a"), pulse(id="b"))
# Avoid - too many steps
sequence(fadeIn(id="a"), slideIn(id="b"), shake(id="c"), bounce(id="d"), fadeOut(id="e"))
Provide visual feedback : Use animations to acknowledge user actions
submit_btn.on_click = sequence(
pulse(id="submit-btn", scale=0.95, duration=100), # Button press feedback
fadeOut(id="form", duration=300),
fadeIn(id="success-message", duration=300)
)
| 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
,
distance
,
duration
|
slideOut
|
Slide element out |
id
,
direction
,
distance
,
duration
|
scaleIn
|
Scale element in |
id
,
from_scale
,
duration
|
scaleOut
|
Scale element out |
id
,
to_scale
,
duration
|
shake
|
Shake effect |
id
,
intensity
,
duration
|
bounce
|
Bounce effect |
id
,
distance
,
duration
|
pulse
|
Pulse/heartbeat |
id
,
scale
,
duration
,
iterations
|
rotate
|
Rotate element |
id
,
degrees
,
duration
|
flip
|
Flip on axis |
id
,
axis
,
duration
|
colorChange
|
Change color |
id
,
property
,
from_color
,
to_color
|
morphSize
|
Change size |
id
,
to_width
,
to_height
,
duration
|
sequence
|
Chain animations |
*animations
|
All animations return
dScript
objects and can be used with
.then()
for advanced chaining.