CxJS is a feature-rich JavaScript (TypeScript) framework for building complex web front-ends, such as portals, dashboards and admin applications. (Key articles only)
# What is CxJS
CxJS is a high-level TypeScript framework for building data-intensive web applications. Built on top of React, it provides widgets, forms, grids, charts, routing, and state management out of the box.
## Framework vs Library
Unlike React, which is a library focused on rendering UI, CxJS is a full-featured framework. You don't need to search for compatible packages or worry about integration issues. Everything works together seamlessly.
| React (Library) | CxJS (Framework) |
| ------------------------------------- | ----------------------------- |
| UI rendering only | Full application stack |
| Choose your own router, forms, tables | Router, forms, grids included |
| Integrate multiple packages | Single cohesive package |
| Flexible, but requires decisions | Opinionated, but productive |
## Built for Business Applications
CxJS is designed for rapid development of business applications that typically include:
- **Forms** with validation, labels, and various input types
- **Data tables** with sorting, filtering, grouping, and inline editing
- **Charts** for data visualization
- **Complex layouts** with navigation, tabs, and overlays
If your application has many tables, forms, and charts, CxJS will significantly speed up your development.
```tsx
import {
Controller,
createModel,
enableCultureSensitiveFormatting,
expr,
tpl,
} from "cx/ui";
import { Grid } from "cx/widgets";
enableCultureSensitiveFormatting();
interface SaleRecord {
region: string;
product: string;
qty: number;
revenue: number;
}
interface PageModel {
sales: SaleRecord[];
$record: SaleRecord;
$group: {
region: string;
productCount: number;
};
}
const m = createModel();
class PageController extends Controller {
onInit() {
this.store.set(m.sales, [
{ region: "Europe", product: "Widget A", qty: 50, revenue: 2500 },
{ region: "Europe", product: "Widget B", qty: 30, revenue: 1800 },
{ region: "Europe", product: "Gadget X", qty: 20, revenue: 3200 },
{ region: "Americas", product: "Widget A", qty: 80, revenue: 4000 },
{ region: "Americas", product: "Widget B", qty: 45, revenue: 2700 },
{ region: "Americas", product: "Gadget X", qty: 35, revenue: 5600 },
{ region: "Asia", product: "Widget A", qty: 120, revenue: 6000 },
{ region: "Asia", product: "Widget B", qty: 60, revenue: 3600 },
{ region: "Asia", product: "Gadget X", qty: 40, revenue: 6400 },
]);
}
}
export default (
);
```
## Battle-Tested
CxJS has been used in production for years, powering admin dashboards, business intelligence tools, data management applications, and internal enterprise tools. The framework is mature, stable, and continuously improved based on real-world usage.
Beyond the rich widget library, CxJS offers declarative data binding, client-side routing, TypeScript-first development with full type safety, and theming support with multiple built-in themes.
---
# Hello World
Let's build a simple interactive example to see CxJS in action.
## Your First Example
CxJS uses JSX syntax similar to React. Here's a basic component with a text field and data binding:
```tsx
import { DateField, TextField } from "cx/widgets";
import { createModel, expr, hasValue, LabelsTopLayout } from "cx/ui";
import { dateDiff } from "cx/util";
interface PageModel {
name: string;
dob: string;
}
const m = createModel();
export default (
`Hello ${name ?? "World"}`)} />
{
if (!dob) return "-";
let diff = dateDiff(new Date(), new Date(dob));
let years = diff / (365.25 * 24 * 60 * 60 * 1000);
return `You're approximately ${years.toFixed(1)} years old...`;
})}
/>
);
```
Let's break down the key parts:
- **PageModel** - TypeScript interface defining the shape of your data
- **TextField**, **DateField** - CxJS form widgets for text and date input
- **LabelsTopLayout** - A layout that positions labels above form fields
- **expr()** - Creates a computed expression that updates when dependencies change
- **hasValue()** - Returns true if the bound value is defined
- **createModel** - Creates a typed accessor model for store bindings
When the user enters their name or date of birth, the bindings update the store and the UI re-renders automatically. This declarative data binding is a core concept in CxJS—you define what data each widget uses, and the framework handles synchronization.
---
# Installation
CxJS is distributed as npm packages and works with modern build tools like Vite and webpack.
## Packages
The main packages you'll need:
| Package | Description |
| ---------- | ----------------------------------------------------- |
| `cx` | Core framework with widgets, charts, and data-binding |
| `cx-react` | React integration for rendering |
Install both packages:
```bash
npm install cx cx-react
```
### Themes
CxJS includes several [themes](/intro/themes). Install one to get started:
```bash
npm install cx-theme-aquamarine
```
## TypeScript Configuration
CxJS is written in TypeScript and provides full type definitions. Configure your `tsconfig.json`:
```json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "cx",
"moduleResolution": "bundler",
"esModuleInterop": true
}
}
```
The key setting is `jsxImportSource: "cx"` which enables CxJS-specific JSX types and attributes like `visible`, `controller`, `layout`, and data-binding functions.
## Build Configuration
### Vite
Vite is the recommended build tool for new projects. Create `vite.config.ts`:
```typescript
import { defineConfig } from "vite";
export default defineConfig({
esbuild: {
jsxImportSource: "cx",
},
});
```
### webpack
For webpack projects, configure TypeScript and JSX handling:
```javascript
module.exports = {
module: {
rules: [
{
test: /\.(ts|tsx)$/,
loader: "ts-loader",
},
],
},
};
```
For large applications, consider using `swc-loader` instead of `ts-loader` for faster builds:
```javascript
module.exports = {
module: {
rules: [
{
test: /\.(ts|tsx)$/,
loader: "swc-loader",
options: {
jsc: {
transform: {
react: {
runtime: "automatic",
importSource: "cx",
},
},
},
},
},
],
},
};
```
## Entry Point
In your main entry file, import the theme and start the application:
```tsx
import { startAppLoop } from "cx/ui";
import { Store } from "cx/data";
import "cx-theme-aquamarine/dist/index.css";
const store = new Store();
startAppLoop(
document.getElementById("app"),
store,
Welcome to CxJS
);
```
---
# Tailwind CSS
CxJS works well with Tailwind CSS. Use Tailwind's utility classes for layout and custom styling while leveraging CxJS widgets for complex UI components like grids, forms, and charts.
## Using with CxJS
Apply Tailwind classes directly to CxJS components using the `class` attribute:
```tsx
import { TextField, Button } from "cx/widgets";
import { createModel } from "cx/ui";
interface PageModel {
name: string;
}
const m = createModel();
export default (
);
```
Tailwind is particularly useful for:
- **Page layouts** - Use flexbox and grid utilities
- **Spacing** - Apply margin and padding with utility classes
- **Custom components** - Style wrappers and containers around CxJS widgets
CxJS themes handle widget internals (inputs, dropdowns, grids), while Tailwind handles the surrounding layout and custom elements.
## Installation
Install Tailwind CSS and its dependencies:
```bash
npm install tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
## Configuration
Configure `tailwind.config.js` to scan your source files:
```javascript
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
```
Create a `tailwind.css` file with proper layer setup. Tailwind 4 uses CSS layers, and it's important to define a `cxjs` layer between `base` and `utilities` so CxJS styles have the correct specificity:
```css
@layer theme, base, cxjs, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css" layer(utilities);
```
Then import your CxJS theme styles into the `cxjs` layer in your main stylesheet:
```css
@import "./tailwind.css";
@layer cxjs {
@import "cx-theme-aquamarine/src/variables";
@import "cx-theme-aquamarine/src/index";
}
```
## Build Setup
### Vite
Vite has built-in PostCSS support. After running `npx tailwindcss init -p`, it creates a `postcss.config.js` file that Vite picks up automatically:
```javascript
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
```
### webpack
For webpack, install the PostCSS loader:
```bash
npm install postcss-loader
```
Add PostCSS to your CSS/SCSS rule:
```javascript
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader", "postcss-loader"],
},
],
},
};
```
## Template
For a complete example, see the [CxJS Tailwind CSS Template](https://github.com/codaxy/cxjs-tailwindcss-template) which includes a pre-configured webpack setup with layouts, dashboards, and sample pages.
---
# JSX Syntax
CxJS uses JSX to define user interfaces declaratively. If you're familiar with React,
you'll find the syntax familiar — with some CxJS-specific extensions.
## Widgets vs HTML Elements
CxJS follows a simple naming convention — widgets start with an uppercase letter (`Button`, `TextField`, `Grid`) while HTML elements start with a lowercase letter (`div`, `span`, `section`). You can freely mix them in the same component.
CxJS widgets also support common attributes for controlling behavior and appearance. Use `visible` to conditionally render widgets, `class` or `className` for CSS classes, and `style` for inline styles:
```tsx
import { createModel } from "cx/data";
import { Button, Switch, TextField } from "cx/widgets";
interface PageModel {
name: string;
showMessage: boolean;
}
const m = createModel();
export default (
Mixing Widgets and HTML
Visibility Control
Show message
This text is conditionally visible!
);
```
## The `` Wrapper
In earlier versions of CxJS, widget trees had to be wrapped in a `` element to instruct
the Babel compiler to process them as CxJS configuration. With TypeScript and the new
`jsxImportSource: "cx"` configuration, this wrapper is no longer required:
```tsx
import { createModel } from "cx/data";
import { Button, TextField } from "cx/widgets";
interface PageModel {
name: string;
}
const m = createModel();
export default (
);
```
The legacy `` syntax is still supported for backwards compatibility:
```tsx
import { createModel } from "cx/data";
import { Button, TextField } from "cx/widgets";
interface PageModel {
name: string;
}
const m = createModel();
export default (
);
```
## Key Differences from React
| Feature | React | CxJS |
| ----------------- | ----------------- | ---------------------------- |
| Class names | `className` only | `class` or `className` |
| Two-way binding | Manual | Built-in via accessor chains |
| State management | `useState`, Redux | Store with typed models |
| Component wrapper | None | `` (optional) |
---
# Typed Models
CxJS uses **typed models** to provide type-safe access to data in the store. Instead of using string paths like `"user.firstName"`, you use accessor chains like `m.user.firstName` that are checked by TypeScript.
## Creating a Model Proxy
Use `createModel()` to create a proxy object that mirrors your data structure. The proxy doesn't hold any data — it generates binding paths that connect widgets to the store.
```tsx
import { createModel } from "cx/data";
import { TextField, Button } from "cx/widgets";
interface User {
firstName: string;
lastName: string;
}
interface PageModel {
user: User;
message: string;
}
const m = createModel();
export default (
Store content
JSON.stringify(data, null, 2)} />
);
```
When you write `m.user.firstName`, CxJS creates a binding to the path `"user.firstName"` in the store. The TextField reads and writes to this path automatically.
## Why Typed Models?
Typed models provide several benefits over string-based paths:
- **Type safety** — TypeScript catches typos and invalid paths at compile time
- **Autocomplete** — Your editor suggests available properties as you type
- **Refactoring** — Rename a property and all usages update automatically
- **Documentation** — Hover over a property to see its type
## Accessor Methods
Accessor chains provide two useful methods for working with paths:
| Method | Description |
| ------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `toString()` | Returns the full string path represented by the accessor. Useful when you need to pass paths to APIs that expect strings. |
| `nameOf()` | Returns only the last segment of the path (the property name). |
```tsx
import { createModel } from "cx/data";
interface User {
firstName: string;
lastName: string;
email: string;
}
interface PageModel {
user: User;
count: number;
}
const m = createModel();
export default (
Accessor
Result
m.user.firstName.toString()
"{m.user.firstName.toString()}"
m.user.email.toString()
"{m.user.email.toString()}"
m.count.toString()
"{m.count.toString()}"
m.user.lastName.nameOf()
"{m.user.lastName.nameOf()}"
m.count.nameOf()
"{m.count.nameOf()}"
);
```
## Nested Structures
Accessor chains work with deeply nested structures. Define your interfaces to match your data shape:
```tsx
interface Address {
street: string;
city: string;
country: string;
}
interface User {
name: string;
address: Address;
}
interface PageModel {
user: User;
}
const m = createModel();
// Access nested properties
m.user.address.city; // binds to "user.address.city"
```
The proxy automatically generates the correct path regardless of nesting depth.
`createModel` can also be imported from `cx/ui`. `createAccessorModelProxy` is available as an alias for backward compatibility.
---
# Store
The **Store** is the central state container in CxJS. It holds all application data and notifies widgets when data changes, triggering automatic UI updates.
## Accessing the Store
Every CxJS widget has access to the store through its instance. In event handlers, access it via the second parameter:
```tsx
import { createModel, Store } from "cx/data";
import { Button } from "cx/widgets";
interface PageModel {
count: number;
}
const m = createModel();
export default (
Store content
JSON.stringify(data, null, 2)} />
);
```
The store is available in event handlers like `onClick`, `onChange`, and others. Use `store.get()` to read values and `store.set()` to write them.
When using typed models with `createModel`, all store methods are fully typed. TypeScript catches type mismatches at compile time:
```tsx
interface PageModel {
count: number;
name: string;
}
const m = createModel();
store.set(m.count, 5); // ✓ OK
store.set(m.count, "five"); // ✗ Type error
store.set(m.name, "John"); // ✓ OK
```
## Store Methods
The store provides several methods for working with data:
| Method | Description |
| ------------------------------ | ------------------------------------------------------------------ |
| `get(accessor)` | Returns the value at the given path |
| `set(accessor, value)` | Sets the value at the given path |
| `init(accessor, value)` | Sets the value only if currently undefined |
| `update(accessor, fn)` | Applies a function to the current value and stores the result |
| `delete(accessor)` | Removes the value at the given path |
| `toggle(accessor)` | Inverts a boolean value |
| `copy(from, to)` | Copies a value from one path to another |
| `move(from, to)` | Moves a value from one path to another |
| `batch(fn)` | Batches multiple updates, notifying listeners only once at the end |
| `silently(fn)` | Executes updates without triggering any notifications |
| `notify(path?)` | Manually triggers change notifications |
| `subscribe(fn)` | Registers a listener for changes; returns an unsubscribe function |
| `ref(accessor, defaultValue?)` | Creates a reactive reference to store data |
| `getData()` | Returns the entire store data object |
```tsx
import { createModel } from "cx/data";
import { Button } from "cx/widgets";
interface PageModel {
user: {
name: string;
age: number;
};
}
const m = createModel();
export default (
Store content
JSON.stringify(data, null, 2)} />
);
```
## Immutability
The store treats all data as immutable. When you call `set()` or `update()`, CxJS creates new object references along the path to the changed value. This enables efficient change detection — widgets only re-render when their bound data actually changes.
When updating objects or arrays, you must create new instances. Mutating
existing objects directly will not trigger UI updates.
```tsx
// Updating an object - spread the original and override properties
store.update(m.user, (user) => ({ ...user, name: "John" }));
// Adding to an array - create a new array
store.update(m.items, (items) => [...items, newItem]);
// Removing from an array - filter returns a new array
store.update(m.items, (items) => items.filter((item) => item.id !== id));
```
## Immer Integration
For complex nested updates, manually spreading objects can become tedious. The `cx-immer` package provides a `mutate` method that lets you write mutable-style code while CxJS handles immutability behind the scenes:
```tsx
import { enableImmerMutate } from "cx-immer";
enableImmerMutate();
// Now you can use mutate with mutable syntax
store.mutate(m.user, (user) => {
user.name = "John";
user.scores.push(100);
user.address.city = "New York";
});
```
The `mutate` method uses [Immer](https://immerjs.github.io/immer/) to produce immutable updates from mutable code. This is especially useful when updating deeply nested structures or performing multiple changes at once.
## Creating a Store
In most applications, CxJS creates the store automatically. If you need to create one manually (for testing or advanced scenarios), use the `Store` constructor:
```tsx
import { Store } from "cx/data";
const store = new Store({
data: {
count: 0,
user: { name: "Guest" },
},
});
// Read and write data
const count = store.get(m.count);
store.set(m.count, count + 1);
```
---
# Data Binding
Data binding connects your UI to the store, enabling automatic synchronization between widgets and application state. When store data changes, bound widgets update automatically. When users interact with widgets, their changes flow back to the store.
## Accessor Chains
The primary way to bind data in CxJS is through **accessor chains** created with `createModel`. Pass an accessor directly to widget properties for two-way binding:
```tsx
import { createModel } from "cx/data";
import { TextField, Slider } from "cx/widgets";
interface PageModel {
name: string;
volume: number;
}
const m = createModel();
export default (
Store content
JSON.stringify(data, null, 2)} />
);
```
When you assign `m.name` to the `value` property, CxJS creates a two-way binding. The TextField displays the current value and writes changes back to the store.
### Default Values with bind
Use `bind` to provide a default value when the store path is undefined:
```tsx
import { createModel } from "cx/data";
import { bind } from "cx/ui";
import { TextField, NumberField } from "cx/widgets";
interface PageModel {
username: string;
count: number;
}
const m = createModel();
export default (
Username:
Count:
Store content
JSON.stringify(data, null, 2)} />
);
```
When the widget initializes, if the store path is undefined, the default value is automatically written to the store.
## Computed Values with expr
Use `expr` to compute values from one or more store paths. The function recalculates whenever any of its dependencies change:
```tsx
import { createModel } from "cx/data";
import { TextField, NumberField } from "cx/widgets";
import { expr } from "cx/ui";
interface PageModel {
firstName: string;
lastName: string;
price: number;
quantity: number;
}
const m = createModel();
export default (
Full name:
`${first || ""} ${last || ""}`.trim(),
)}
/>
Total: {
let total = (price || 0) * (qty || 0);
return `$${total.toFixed(2)}`;
})}
/>
);
```
The `expr` function takes accessor chains as arguments, followed by a compute function that receives the current values:
```tsx
expr(m.firstName, m.lastName, (first, last) => `${first} ${last}`);
```
## Computed Values with computable
For complex calculations, use `computable` instead of `expr`. It works the same way but adds **memoization** — the result is cached and only recalculated when dependencies actually change:
```tsx
import { computable } from "cx/data";
// Memoized computation - result cached until items or taxRate changes
const total = computable(m.items, m.taxRate, (items, taxRate) => {
const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0);
return subtotal * (1 + taxRate);
});
```
Use `computable` when the calculation is expensive or when the same value is used in multiple places.
## Formatting Values
Use `format` to apply format strings to bound values:
```tsx
import { format } from "cx/ui";
```
Use `tpl` to combine multiple values into formatted text:
```tsx
import { tpl } from "cx/ui";
// Positional placeholders
// With formatting
// With null fallback
```
See [Formatting](/docs/intro/formatting) for the complete format syntax reference.
## Expression Helpers
CxJS provides type-safe helper functions for common boolean expressions. These return `Selector` and are useful for properties like `visible`, `disabled`, and `readOnly`:
```tsx
import { truthy, isEmpty, greaterThan } from "cx/ui";
User has a name
No items available
User is an adult
```
| Helper | Description |
| ------------------------------------- | ------------------------------------------------------ |
| `truthy(accessor)` | Evaluates truthiness |
| `falsy(accessor)` | Evaluates falsiness |
| `isTrue(accessor)` | Strict `true` check |
| `isFalse(accessor)` | Strict `false` check |
| `hasValue(accessor)` | Checks for non-null/undefined |
| `isEmpty(accessor)` | Checks for empty strings/arrays |
| `isNonEmpty(accessor)` | Checks for non-empty strings/arrays |
| `equal(accessor, value)` | Loose equality comparison |
| `notEqual(accessor, value)` | Loose inequality comparison |
| `strictEqual(accessor, value)` | Strict equality comparison |
| `strictNotEqual(accessor, value)` | Strict inequality comparison |
| `greaterThan(accessor, value)` | Numeric greater than |
| `lessThan(accessor, value)` | Numeric less than |
| `greaterThanOrEqual(accessor, value)` | Numeric greater than or equal |
| `lessThanOrEqual(accessor, value)` | Numeric less than or equal |
| `format(accessor, formatString)` | Formats value using [format strings](/core/formatting) |
## Legacy Binding Syntax
The following binding methods are supported for backwards compatibility but
are not recommended for new code.
### String-based bind
Before typed models, bindings used string paths:
```tsx
import { bind } from "cx/data";
// Legacy string-based binding
// Modern accessor chain (preferred)
```
### String-path templates
The `tpl` function also supports string-path syntax:
```tsx
import { tpl } from "cx/data";
// Legacy string-path template
// Modern typed accessor (preferred)
```
### Attribute suffixes
In older CxJS code, you may see attribute suffixes like `-bind`, `-expr`, and `-tpl`. These require Babel plugins and are not supported in the TypeScript-first approach:
```tsx
// Legacy attribute suffixes (requires Babel plugin)
```
---
# Controllers
Controllers contain the business logic for your views. They handle data initialization, event callbacks, computed values, and reactions to data changes.
## Creating a Controller
Extend the `Controller` class and attach it to a widget using the `controller` property. The controller has access to the store and can define methods that widgets call:
```tsx
import { createModel } from "cx/data";
import { Controller } from "cx/ui";
import { Button, TextField } from "cx/widgets";
interface PageModel {
name: string;
greeting: string;
}
const m = createModel();
class PageController extends Controller {
onInit() {
this.store.init(m.name, "World");
}
greet() {
let name = this.store.get(m.name);
this.store.set(m.greeting, `Hello, ${name}!`);
}
clear() {
this.store.delete(m.greeting);
}
}
export default (
Store content
JSON.stringify(data, null, 2)} />
);
```
The controller's methods are available to all widgets within its scope. In event handlers, access the controller through the second parameter.
## Inline Controllers
For simple cases, define a controller inline using an object:
```tsx
import { createModel } from "cx/data";
import { NumberField } from "cx/widgets";
import { tpl } from "cx/ui";
interface PageModel {
count: number;
double: number;
}
const m = createModel();
export default (
c * 2);
},
}}
class="flex items-center gap-4"
>
);
```
The inline form supports lifecycle methods and controller features like `addTrigger` and `addComputable`.
## Lifecycle Methods
Controllers have lifecycle methods that run at specific times:
| Method | Description |
| ------------- | -------------------------------------------------------------------------------- |
| `onInit()` | Runs once when the controller is created. Use for data initialization and setup. |
| `onExplore()` | Runs on every render cycle during the explore phase. |
| `onDestroy()` | Runs when the controller is destroyed. Use for cleanup (timers, subscriptions). |
```tsx
class PageController extends Controller {
timer: number;
onInit() {
// Initialize data
this.store.init(m.count, 0);
// Start a timer
this.timer = window.setInterval(() => {
this.store.update(m.count, (c) => c + 1);
}, 1000);
}
onDestroy() {
// Clean up
window.clearInterval(this.timer);
}
}
```
## Typed Controller Access
Use `getControllerByType` to get a typed reference to a controller. This provides full autocomplete and compile-time type checking:
```tsx
import { createModel } from "cx/data";
import { Controller } from "cx/ui";
import { Button } from "cx/widgets";
interface PageModel {
count: number;
}
const m = createModel();
class CounterController extends Controller {
onInit() {
this.store.init(m.count, 0);
}
increment(amount: number = 1) {
this.store.update(m.count, (count) => count + amount);
}
decrement(amount: number = 1) {
this.store.update(m.count, (count) => count - amount);
}
reset() {
this.store.set(m.count, 0);
}
}
export default (
);
```
The `getControllerByType` method searches up the widget tree and returns a typed controller instance.
## Triggers
Triggers watch store paths and run callbacks when values change. Use `addTrigger` in `onInit`:
```tsx
class PageController extends Controller {
onInit() {
this.addTrigger(
"selection-changed",
[m.selectedId],
(selectedId) => {
if (selectedId) {
this.loadDetails(selectedId);
}
},
true,
); // true = run immediately
}
async loadDetails(id: string) {
let data = await fetch(`/api/items/${id}`).then((r) => r.json());
this.store.set(m.details, data);
}
}
```
The trigger name allows you to remove it later with `removeTrigger("selection-changed")`.
## Computables
Add computed values that automatically update when dependencies change:
```tsx
class PageController extends Controller {
onInit() {
this.addComputable(m.fullName, [m.firstName, m.lastName], (first, last) => {
return `${first || ""} ${last || ""}`.trim();
});
this.addComputable(m.total, [m.items], (items) => {
return items?.reduce((sum, item) => sum + item.price, 0) || 0;
});
}
}
```
The first argument is the store path where the result is written. The computed value updates whenever any dependency changes.
## Accessing Parent Controllers
Use `getParentControllerByType` to get a typed reference to a parent controller:
```tsx
class ChildController extends Controller {
onSave() {
let parent = this.getParentControllerByType(PageController);
parent.saveChild(this.getData());
}
}
```
This provides full type safety and autocomplete. For dynamic method invocation by name, use `invokeParentMethod`:
```tsx
this.invokeParentMethod("onSave", this.getData());
```
---
# Formatting
CxJS provides built-in support for formatting numbers, dates, and currencies. Formats can be applied to widgets via the `format` property or programmatically using `Format.value()`.
## Using Formats
Widgets like `NumberField` and `DateField` accept a `format` property that controls how values are displayed:
```tsx
import { createModel } from "cx/data";
import { Format } from "cx/util";
import { bind, expr, format, LabelsTopLayout } from "cx/ui";
import { NumberField, DateField } from "cx/widgets";
interface PageModel {
price: number;
quantity: number;
date: Date;
}
const m = createModel();
export default (
Format
Result
currency;USD;2
n;0
d;yyMd
d;DDDDyyyyMMMMd
Format.value(date, "d;DDDDyyyyMMMMd"))}
/>
);
```
For formatting bound values in text, see the [Formatting Values](/docs/intro/data-binding#formatting-values) section in Data Binding.
## Culture-Sensitive Formatting
Date, currency, and number formats depend on culture settings. Enable culture-sensitive formatting before use:
```tsx
import { enableCultureSensitiveFormatting } from "cx/ui";
enableCultureSensitiveFormatting();
```
This ensures formats respect locale-specific conventions for decimal separators, date order, currency symbols, and month/day names.
## Format Syntax
Format strings use semicolons to separate parameters:
```
formatType;param1;param2
```
Use a pipe `|` to specify text for null values:
```
n;2|N/A
```
Chain multiple formats with colons (applied left-to-right):
```
n;2:suffix; USD
```
## Number Formats
Number formats support min and max decimal places, plus optional flags: `n;minDecimals;maxDecimals;flags`. If only one decimal value is provided, it's used for both min and max.
| Format | Description | Example Input | Example Output |
| --------- | --------------------------- | ------------- | -------------- |
| `n` | Number | `1234.5` | `1,234.5` |
| `n;0` | No decimals | `1234.5` | `1,235` |
| `n;2` | Exactly 2 decimals | `1234.5` | `1,234.50` |
| `n;0;2` | 0-2 decimals | `1234.5` | `1,234.5` |
| `n;0;0;+` | Plus sign for positive | `1234` | `+1,234` |
| `n;0;0;a` | Accounting format | `-1234` | `(1,234)` |
| `n;0;0;c` | Compact notation | `105000` | `105K` |
| `p` | Percentage (×100) | `0.25` | `25%` |
| `p;0;2` | Percentage, 0-2 decimals | `0.256` | `25.6%` |
| `ps;0;2` | Percent sign only (no ×100) | `25.6` | `25.6%` |
## Currency Format
The `currency` format supports an optional currency code, decimal places, and flags: `currency;code;minDecimals;maxDecimals;flags`. The currency code can be omitted to use the default currency.
| Format | Description | Example Input | Example Output |
| -------------------- | ---------------------------- | ------------- | -------------- |
| `currency` | Default currency | `1234.5` | `$1,234.50` |
| `currency;USD` | US Dollars | `1234.5` | `$1,234.50` |
| `currency;EUR` | Euros | `1234.5` | `€1,234.50` |
| `currency;USD;0` | USD, no decimals | `1234.5` | `$1,235` |
| `currency;;2` | Default currency, 2 decimals | `1234.5` | `$1,234.50` |
| `currency;USD;2;2;+` | Plus sign for positive | `1234.5` | `+$1,234.50` |
| `currency;USD;2;2;a` | Accounting format | `-1234.5` | `($1,234.50)` |
| `currency;;0;0;c` | Compact notation | `105000` | `$105K` |
## Format Flags
These flags can be added to number and currency formats:
| Flag | Description |
| ---- | -------------------------------------------------- |
| `+` | Display plus sign for positive numbers |
| `a` | Accounting format (negative values in parentheses) |
| `c` | Compact notation (e.g., 105K, 1M) |
Flags can be combined, e.g., `+ac` for all three options. The default currency is determined by culture settings.
## String Formats
String formats allow adding prefixes, suffixes, and wrapping values:
| Format | Description | Example Input | Example Output |
| ------------ | -------------------- | ------------- | -------------- |
| `prefix;Hi ` | Add prefix | `"John"` | `"Hi John"` |
| `suffix; cm` | Add suffix | `180` | `"180 cm"` |
| `wrap;(;)` | Wrap with delimiters | `5` | `"(5)"` |
These can be chained with other formats:
```tsx
Format.value(5, "n;2:wrap;(;)"); // "(5.00)"
Format.value(180, "n;0:suffix; cm"); // "180 cm"
```
## Date Formats
Date formats use pattern codes concatenated together. Separators are provided by the culture settings, not the pattern.
| Format | Description | Example Output |
| ----------------- | ------------------------- | ---------------------------- |
| `d` | Default date | `2/1/2024` |
| `d;yyMd` | Short year | `2/1/24` |
| `d;yyMMdd` | 2-digit year, padded | `02/01/24` |
| `d;yyyyMMdd` | Full date, padded | `02/01/2024` |
| `d;yyyyMMMd` | Abbreviated month | `Feb 1, 2024` |
| `d;yyyyMMMMdd` | Full month name | `February 01, 2024` |
| `d;DDDyyyyMd` | Short weekday | `Thu, 2/1/2024` |
| `d;DDDDyyyyMMMdd` | Full weekday, short month | `Thursday, Feb 01, 2024` |
| `d;DDDDyyyyMMMMd` | Full weekday and month | `Thursday, February 1, 2024` |
### Date Pattern Characters
Pattern codes are concatenated without separators. Four characters means full name, three is abbreviated, two is padded, one is numeric.
| Character | Description |
| --------- | ------------------- |
| `yyyy` | 4-digit year |
| `yy` | 2-digit year |
| `MMMM` | Full month name |
| `MMM` | Abbreviated month |
| `MM` | 2-digit month |
| `M` | Month number |
| `dd` | 2-digit day |
| `d` | Day number |
| `DDDD` | Full weekday name |
| `DDD` | Abbreviated weekday |
### Time Pattern Characters
| Character | Description |
| --------- | ------------------- |
| `HH` | Hours (padded) |
| `H` | Hours |
| `mm` | Minutes (padded) |
| `m` | Minutes |
| `ss` | Seconds (padded) |
| `s` | Seconds |
| `a` / `A` | AM/PM indicator |
| `N` | 24-hour format flag |
Date and time patterns can be combined: `d;yyyyMMddHHmm` formats both date and time. Add `N` for 24-hour format: `d;yyyyMMddNHHmm`.
For more details on culture-sensitive formatting, see [intl-io](https://github.com/codaxy/intl-io).
## Programmatic Formatting
Use `Format.value()` to format values in code:
```tsx
import { Format } from "cx/util";
Format.value(1234.5, "n;2"); // "1,234.50"
Format.value(0.15, "p;0"); // "15%"
Format.value(new Date(), "d;yyyyMMdd"); // "02/01/2024"
```
Use the `format` helper to format bound values:
```tsx
import { format } from "cx/ui";
;
```
Alternatively, use `expr` with `Format.value` for more control:
```tsx
import { expr } from "cx/ui";
import { Format } from "cx/util";
Format.value(price, "currency;USD"))} />;
```
## String Templates
Use `StringTemplate.format` when you need to combine multiple values into a single formatted string:
```tsx
import { StringTemplate } from "cx/util";
// Positional arguments
StringTemplate.format(
"{0} bought {1} items for {2:currency;USD}",
"John",
5,
49.99,
);
// "John bought 5 items for $49.99"
// Named properties with an object
StringTemplate.format("{name} - {date:d;yyyyMMdd}", {
name: "Report",
date: new Date(),
});
// "Report - 02/01/2024"
```
Use `StringTemplate.compile` to create a reusable formatter function:
```tsx
const formatter = StringTemplate.compile("{name}: {value:currency;USD}");
formatter({ name: "Total", value: 99.99 }); // "Total: $99.99"
formatter({ name: "Tax", value: 7.5 }); // "Tax: $7.50"
```
## Custom Formats
Register custom formats using `Format.register`:
```tsx
import { Format } from "cx/util";
// Simple format
Format.register("brackets", (value) => `(${value})`);
// Use it
Format.value("test", "brackets"); // "(test)"
```
For formats with parameters, use `Format.registerFactory`:
```tsx
Format.registerFactory("suffix", (format, suffix) => {
return (value) => value + suffix;
});
// Use it
Format.value(100, "suffix; kg"); // "100 kg"
```
---
# HtmlElement
The `HtmlElement` widget renders HTML elements with CxJS data binding support. The CxJS JSX runtime automatically converts all lowercase elements (like `div`, `span`, `p`) to `HtmlElement` instances with the corresponding `tag` property set.
You can also use `HtmlElement` directly when you need to specify the tag dynamically or prefer explicit syntax.
HTML elements can be freely mixed with CxJS widgets like `TextField`, allowing you to build forms and layouts that combine standard HTML with rich interactive components.
```tsx
import { createModel } from "cx/data";
import { tpl } from "cx/ui";
import { HtmlElement, TextField } from "cx/widgets";
interface Model {
name: string;
}
const m = createModel();
export const model = {
name: "World",
};
export default (
Heading
Paragraph with some text.
Using HtmlElement directly
);
```
## Key Features
- Lowercase JSX elements are automatically converted to `HtmlElement` by the JSX runtime
- All standard HTML attributes and events work as expected
- CxJS-specific attributes like `visible`, `layout`, `controller` are supported
- Use `text` prop with `tpl()` for data-bound text content
- Mix freely with CxJS widgets
## Configuration
| Property | Type | Description |
| -------- | ---- | ----------- |
| `tag` | `string` | Name of the HTML element to render. Default is `div`. |
| `text` / `innerText` | `string` | Inner text contents. |
| `innerHtml` / `html` | `string` | HTML to be injected into the element. |
| `tooltip` | `string \| object` | Tooltip configuration. |
| `autoFocus` | `boolean` | Set to `true` to automatically focus the element when mounted. |
| `baseClass` | `string` | Base CSS class to be applied to the element. |
---
# PureContainer
`PureContainer` groups multiple widgets together without adding any HTML markup to the DOM. This is useful when you need to control visibility or apply a layout to a group of elements.
```tsx
import { createModel } from "cx/data";
import { LabelsTopLayout, PureContainer } from "cx/ui";
import { Checkbox, TextField } from "cx/widgets";
interface Model {
showContactInfo: boolean;
email: string;
phone: string;
}
const m = createModel();
export const model = {
showContactInfo: true,
email: "",
phone: "",
};
export default (
Show contact information
Contact Information
We'll never share your contact information.
);
```
## Common Use Cases
- Toggle visibility of multiple widgets at once using `visible`
- Apply a shared `layout` to a group of form fields
- Base class for other CxJS components like `ValidationGroup`, `Repeater`, and `Route`
## Configuration
| Property | Type | Description |
| -------- | ---- | ----------- |
| `visible` | `boolean` | Controls visibility of all children. |
| `layout` | `string \| object` | Inner layout applied to children. |
| `items` / `children` | `array` | List of child elements. |
| `controller` | `object` | Controller instance for this container. |
| `trimWhitespace` | `boolean` | Remove whitespace in text children. Default is `true`. |
| `preserveWhitespace` / `ws` | `boolean` | Keep whitespace in text children. Default is `false`. |
---
# ContentResolver
`ContentResolver` dynamically resolves content at runtime based on data. Use it when the content to display is unknown at build time, depends on data values, or needs to be lazy loaded.
```tsx
import { createModel } from "cx/data";
import {
Checkbox,
ContentResolver,
DateField,
LookupField,
Switch,
TextField,
} from "cx/widgets";
interface Model {
fieldType: string;
text: string;
date: string;
checked: boolean;
}
const m = createModel();
const fieldTypes = [
{ id: "textfield", text: "TextField" },
{ id: "datefield", text: "DateField" },
{ id: "checkbox", text: "Checkbox" },
{ id: "switch", text: "Switch" },
];
export default (
{
switch (type) {
case "textfield":
return ;
case "datefield":
return ;
case "checkbox":
return Checked;
case "switch":
return ;
default:
return null;
}
}}
/>
);
```
## How It Works
1. The `params` prop binds to a value in the store
2. When `params` changes, `onResolve` is called with the new value
3. `onResolve` returns the widget configuration to render
4. For async loading, `onResolve` can return a Promise
5. Children are displayed as default content while loading
## Structured Params
The `params` prop can be a structured object with multiple bindings:
```jsx
{
// resolve based on multiple parameters
}}
/>
```
When any of the bound values change, `onResolve` is called with the updated object.
## Configuration
| Property | Type | Description |
| -------- | ---- | ----------- |
| `params` | `any` | Parameter binding. Can be a single value or structured object. Content is recreated when params change. |
| `onResolve` | `function` | Callback taking `params` and returning widget configuration or a Promise. |
| `mode` | `string` | How resolved content combines with children: `replace`, `prepend`, or `append`. Default is `replace`. |
| `loading` | `boolean` | Writable binding set to `true` while a Promise is resolving. |
| `children` | `any` | Default content displayed while `onResolve` Promise is loading. |
---
# Functional Components
Functional components provide a simple way to create reusable structures composed of CxJS widgets. Use `createFunctionalComponent` to define a CxJS functional component:
```tsx
import { createModel } from "cx/data";
import type { AccessorChain } from "cx/ui";
import { bind, createFunctionalComponent } from "cx/ui";
import { Button } from "cx/widgets";
interface PageModel {
count1: number;
count2: number;
}
const m = createModel();
interface CounterProps {
value: AccessorChain;
label: string;
}
const Counter = createFunctionalComponent(({ value, label }: CounterProps) => (
{label}:
));
export default (
);
```
The `Counter` component can be reused with different props, each instance maintaining its own state through different store bindings.
## Example 2
Functional components are useful for creating reusable chart configurations:
```tsx
import { createFunctionalComponent } from "cx/ui";
import { Svg } from "cx/svg";
import { Chart, Gridlines, LineGraph, NumericAxis } from "cx/charts";
interface LineChartProps {
data: { x: number; y: number }[];
chartStyle?: string;
lineStyle?: string;
areaStyle?: string;
}
const LineChart = createFunctionalComponent(
({ data, chartStyle, lineStyle, areaStyle }: LineChartProps) => (
),
);
export default (
);
```
The same `LineChart` component renders three different chart styles by passing different props.
## Conditional Logic
Functional components can contain conditional logic:
```tsx
import { createModel } from "cx/data";
import {
createFunctionalComponent,
LabelsLeftLayout,
LabelsTopLayout,
} from "cx/ui";
import { TextField } from "cx/widgets";
interface PageModel {
form: {
firstName: string;
lastName: string;
};
}
const m = createModel();
interface MyFormProps {
vertical?: boolean;
}
const MyForm = createFunctionalComponent(({ vertical }: MyFormProps) => {
let layout = !vertical
? LabelsLeftLayout
: { type: LabelsTopLayout, vertical: true };
return (
);
});
export default (
);
```
The `vertical` prop changes the layout at render time.
## Reserved Properties
These properties are handled by the framework and should not be used inside the function body:
| Property | Type | Description |
| ------------- | ------------ | ---------------------------------------------------------------------------------------- |
| `visible` | `boolean` | If `false`, the component won't render and its controller won't initialize. Alias: `if`. |
| `controller` | `Controller` | Controller that will be initialized with the component. |
| `layout` | `Layout` | Inner layout applied to child elements. |
| `outerLayout` | `Layout` | Outer layout that wraps the component. |
| `putInto` | `string` | Content placeholder name for outer layouts. Alias: `contentFor`. |
---
# Data View Components
All application data in CxJS is stored inside a central [Store](/docs/intro/store). While convenient for global state, accessing deeply nested paths or working with collections can become cumbersome. Data View components wrap parts of the widget tree and provide a modified view of the Store data, making it easier to work with specific areas of the data model.
## Comparison
| Component | Purpose | Use case |
| -------------------------------------- | ------------------------------------------------- | ------------------------------------- |
| [Repeater](./repeater) | Renders children for each record in a collection | Lists, tables, any repeated content |
| [Rescope](./rescope) | Selects a common prefix for shorter binding paths | Deeply nested data structures |
| [Sandbox](./sandbox) | Multiplexes data based on a dynamic key | Tabs, routes with isolated page data |
| [PrivateStore](./private-store) | Creates an isolated store for a subtree | Reusable components with local state |
| [DataProxy](./data-proxy) | Creates aliases with custom getter/setter logic | Computed values, data transformations |
| [Route](./route) | Renders children when URL matches a pattern | Page routing, exposes `$route` params |
## How Data Views Work
Each Data View component exposes the same interface as the Store to its children, but can introduce additional properties. For example, Repeater adds `$record` and `$index` for each item in the collection, Route exposes `$route` with matched URL parameters, while Sandbox might expose `$page` for route-specific data. These additional properties are only accessible within the scope of that Data View, allowing child widgets to bind to them just like any other Store data.
## How to Choose
Use [Repeater](./repeater) when you need to render a list of items from an array.
Use [Rescope](./rescope) when working with deeply nested data and you want shorter binding paths.
Use [Sandbox](./sandbox) when you need to switch between different data contexts based on a key (e.g., tabs, route parameters).
Use [PrivateStore](./private-store) (also known as Restate) when you need completely isolated state that doesn't affect the global store.
Use [DataProxy](./data-proxy) when you need to transform data or create computed aliases with custom getter/setter logic.
Use [Route](./route) when you need to conditionally render content based on URL and access matched route parameters.
## Store Mutation
By default, Data View components write aliased data (like `$record`, `$page`) back to the parent store, all the way up to the global store. Regular data bindings propagate normally regardless of these settings.
This default behavior is often fine and can improve performance by avoiding data copying. However, sometimes you want to prevent aliased fields from polluting your data. For example, when rendering a tree with nested Repeaters, fields like `$record` and `$index` would be written into your tree nodes.
Two properties control this behavior:
- `immutable` - Prevents aliased data from being written to the parent store.
- `sealed` - Prevents child Data Views from writing aliased data to this Data View's store.
---
# Repeater
Repeater renders its children for each record in a collection. Use `recordAlias` to specify an accessor for accessing record data within the repeater.
## Example
```tsx
import { createModel } from "cx/data";
import { Controller, expr } from "cx/ui";
import { Button, Checkbox, Repeater } from "cx/widgets";
interface Item {
text: string;
checked: boolean;
}
interface PageModel {
items: Item[];
$record: Item;
}
const m = createModel();
class PageController extends Controller {
onInit() {
this.reset();
}
reset() {
this.store.set(m.items, [
{ text: "Learn CxJS basics", checked: true },
{ text: "Build a sample app", checked: false },
{ text: "Master data binding", checked: false },
]);
}
}
export default (
Completed:{" "}
items.filter((a) => a.checked).length,
)}
/>{" "}
of tasks
);
```
## Typed Model
Add `$record` to your model interface to get type-safe access to record data:
```tsx
interface PageModel {
items: Item[];
$record: Item;
}
const m = createModel();
```
Then use `recordAlias={m.$record}` to bind the record accessor:
```tsx
```
## Accessing the Record Store
Event handlers like `onClick` receive an instance object as the second argument. This instance contains a `store` that provides access to the record-specific data view. Use this to manipulate individual records:
```tsx
);
```
## Touch Devices
Touch devices don't have precise mouse pointer location, so tooltips are shown/hidden when the user taps the element. To make tooltips ignore touch events, set `touchBehavior` to `"ignore"`.
To make all tooltips ignore touch events by default:
```tsx
Tooltip.prototype.touchBehavior = "ignore";
```
## Configuration
| Property | Type | Description |
| -------- | ---- | ----------- |
| `text` | `string` | Text displayed inside the tooltip. |
| `title` | `string` | Text displayed in the tooltip header. |
| `children` / `items` | `any` | Rich content to display inside the tooltip. |
| `placement` | `string` | Placement strategy. Defaults to `"right up down left"`. Also accepts `"top"` and `"bottom"`. |
| `offset` | `number` | Distance in pixels from the related element. Default is `8`. |
| `visible` | `boolean` | Controls tooltip visibility. |
| `alwaysVisible` | `boolean` | Makes the tooltip always visible (useful for product tours). |
| `mouseTrap` | `boolean` | Keeps the tooltip visible while the mouse is inside it. |
| `trackMouse` | `boolean` | Makes the tooltip follow mouse movement. |
| `touchBehavior` | `string` | Controls touch event behavior: `"toggle"` (default) or `"ignore"`. |
| `globalMouseTracking` | `boolean` | Uses window `mousemove` event instead of element events for coordinates. |
---
# Toast
Toasts are non-blocking notifications that appear at the edge of the screen. They inform users about events and can offer quick actions. Unlike modal dialogs, toasts don't interrupt the user's workflow.
```tsx
import { createModel } from "cx/data";
import { Button, Toast } from "cx/widgets";
interface PageModel {
toast: {
visible: boolean;
};
}
const m = createModel();
export default (
{
Toast.create({
message: "This is a toast at the top.",
placement: "top",
timeout: 3000,
}).open(store);
}}
>
Top
{
Toast.create({
message: "This is a toast on the right.",
placement: "right",
timeout: 3000,
}).open(store);
}}
>
Right
{
Toast.create({
message: "This is a toast at the bottom.",
placement: "bottom",
timeout: 3000,
}).open(store);
}}
>
Bottom
{
Toast.create({
message: "This is a toast on the left.",
placement: "left",
timeout: 3000,
}).open(store);
}}
>
Left
);
```
Toasts are created programmatically using `Toast.create()` and opened with the `open(store)` method. The `placement` prop controls which edge of the screen the toast appears on.
## Mods
Toasts support visual modifiers for different notification types: `primary`, `success`, `warning`, and `error`.
```tsx
import { Button, Toast } from "cx/widgets";
export default (
);
```
## Declarative Toasts
Toasts can also be used declaratively with a `visible` binding. This is useful when the toast state needs to be tied to the application store:
```tsx
import { createModel } from "cx/data";
import { Button, Toast, TextField } from "cx/widgets";
interface PageModel {
simpleToast: {
visible: boolean;
};
complexToast: {
visible: boolean;
reply: string;
};
}
const m = createModel();
export default (
{
store.toggle(m.simpleToast.visible);
}}
>
Toggle Simple Toast
{
store.toggle(m.complexToast.visible);
}}
>
Toggle Complex Toast
This toast is controlled by store state.
);
```
Add the `dismiss` prop to buttons that should close the toast when clicked.
## Configuration
| Property | Type | Description |
| -------- | ---- | ----------- |
| `message` | `StringProp` | Text message to display in the toast. |
| `placement` | `string` | Position on screen. Values: `top`, `right`, `bottom`, `left`, `top-left`, `top-right`, `bottom-left`, `bottom-right`. Default is `top`. |
| `timeout` | `NumberProp` | Time in milliseconds before auto-dismiss. If not set, toast stays until manually closed. |
| `pad` | `boolean` | Adds default padding. Default is `true`. |
| `mod` | `string` | Visual modifier. Values: `primary`, `success`, `warning`, `error`. |
| `visible` | `BooleanProp` | Controls toast visibility for declarative usage. |
| `animate` | `boolean` | Enables enter/leave animations. Default is `true`. |
| `destroyDelay` | `number` | Delay in milliseconds before removing from DOM after closing. Default is `300`. |
---
# MsgBox
The `MsgBox` class provides utility methods for displaying alert and confirmation dialogs. Both methods return Promises, making it easy to handle user responses with async/await.
## Alert
Use `MsgBox.alert()` to display informational messages. The method accepts either a simple string or a configuration object.
```tsx
import { Button, MsgBox } from "cx/widgets";
export default (
{
MsgBox.alert("This is a simple alert message.");
}}
>
Simple Alert
{
MsgBox.alert({
title: "Information",
message: "This alert has a custom title.",
});
}}
>
Alert with Title
{
MsgBox.alert({
title: "Custom Button",
message: "Click the button below to close.",
okText: "Got it!",
});
}}
>
Custom OK Text
);
```
## Yes/No Confirmation
Use `MsgBox.yesNo()` to ask the user for confirmation. The returned Promise resolves to `"yes"` or `"no"` based on the user's choice.
```tsx
import { createModel } from "cx/data";
import { Button, MsgBox } from "cx/widgets";
interface PageModel {
result: string;
}
const m = createModel();
export default (
{
const result = await MsgBox.yesNo("Do you want to proceed?");
store.set(m.result, result);
}}
>
Simple Yes/No
{
const result = await MsgBox.yesNo({
title: "Confirm Action",
message: "Are you sure you want to delete this item?",
yesText: "Delete",
noText: "Cancel",
yesButtonMod: "primary",
});
store.set(m.result, result);
}}
>
Custom Buttons
);
```
## Rich Content
For messages with formatted content, links, or images, use the `children` property instead of `message`.
```tsx
import { Button, MsgBox } from "cx/widgets";
export default (
{
MsgBox.alert({
title: "Terms of Service",
children: (
);
```
## Methods
| Method | Description |
| ------ | ----------- |
| `MsgBox.alert(options)` | Displays an alert dialog. Returns a `Promise` that resolves when the user clicks OK. |
| `MsgBox.yesNo(options)` | Displays a confirmation dialog. Returns a `Promise` that resolves to `"yes"` or `"no"`. |
## Options
Both methods accept either a message string or a configuration object with these properties:
| Property | Type | Description |
| -------- | ---- | ----------- |
| `message` | `string` | Text message to display. |
| `title` | `string` | Dialog window title. |
| `header` | `object` | Custom Cx component for the window header. |
| `children` | `any` | Rich content (Cx components) to display instead of a plain message. |
| `items` | `any` | Alias for `children`. |
| `style` | `string` | CSS style for the dialog window. |
| `store` | `Store` | Store instance to use for the dialog. |
| `okText` | `string` | Custom text for the OK button. Default: `"OK"`. |
| `yesText` | `string` | Custom text for the Yes button. Default: `"Yes"`. |
| `noText` | `string` | Custom text for the No button. Default: `"No"`. |
| `yesButtonMod` | `string` | Visual modifier for the Yes button. |
| `noButtonMod` | `string` | Visual modifier for the No button. |
| `callback` | `function` | Optional callback invoked before closing. Return `false` to prevent closing. |
---
# Menu
The `Menu` widget displays a list of options or commands in horizontal or vertical form. Use `Submenu` to create nested menus, and `MenuItem` for individual actions.
Menus are focus-driven — when focus leaves the menu, all submenus close automatically. To programmatically close a menu, call `document.activeElement.blur()`.
## Horizontal Menu
```tsx
import { Menu, Submenu, MenuItem } from "cx/widgets";
export default (
);
```
Use the `horizontal` prop on `Menu` to create a horizontal menu bar. Submenus are defined by nesting a `Menu` with `putInto="dropdown"` inside a `Submenu`. Add `arrow` to submenus to show an expansion indicator.
## Vertical Menu
```tsx
import { Menu, Submenu, MenuItem } from "cx/widgets";
export default (
Settings
);
```
Vertical menus are the default orientation. They work well for sidebar navigation or context menus.
## Icons and Checkboxes
```tsx
import { createModel } from "cx/data";
import { Menu, MenuItem } from "cx/widgets";
import "../../icons/lucide";
interface PageModel {
darkMode: boolean;
notifications: boolean;
autoSave: boolean;
}
const m = createModel();
export default (
);
```
Add `icons` to `Menu` to reserve space for icons. Use `icon` on `MenuItem` for action icons, or `checked` with a binding to create checkbox items.
## Overflow
```tsx
import { Menu, Submenu, MenuItem, MenuSpacer } from "cx/widgets";
export default (
File
Edit
View
Help
);
```
Set `overflow` on horizontal menus to automatically move items that don't fit into an overflow submenu. Use `MenuSpacer` to push items to the right side.
## Menu Configuration
| Property | Type | Description |
| -------- | ---- | ----------- |
| `horizontal` | `boolean` | Set to `true` for horizontal menus. Default is `false`. |
| `itemPadding` | `string` | Size of menu items: `xsmall`, `small`, `medium`, `large`, `xlarge`. Default is `small` for horizontal, `medium` for vertical. |
| `icons` | `boolean` | Reserve space for icons in menu items. Default is `false`. |
| `overflow` | `boolean` | Enable overflow menu for items that don't fit. Horizontal menus only. |
| `overflowIcon` | `string` | Icon for the overflow menu button. |
| `autoFocus` | `boolean` | Auto-focus first menu item on mount. |
## MenuItem Configuration
| Property | Type | Description |
| -------- | ---- | ----------- |
| `autoClose` | `boolean` | Close the menu when this item is clicked. |
| `icon` | `StringProp` | Icon to display before the item text. |
| `checked` | `BooleanProp` | Bind to create a checkbox menu item. |
| `disabled` | `BooleanProp` | Disable the menu item. |
| `arrow` | `BooleanProp` | Show an arrow indicating a submenu. |
| `onClick` | `function` | Handler called when the item is clicked. |
| `tooltip` | `string \| object` | Tooltip configuration. |
| `keyboardShortcut` | `KeyCode` | Register a keyboard shortcut for this item. |
| `openOnFocus` | `boolean` | Open dropdown when focused. Default is `true`. |
| `hoverToOpen` | `boolean` | Open submenu on hover instead of focus. |
| `clickToOpen` | `boolean` | Require click to open submenu instead of hover. |
| `placement` | `string` | Dropdown placement: `up`, `down`, `left`, `right`, or corners like `down-right`. |
| `confirm` | `string \| object` | Show confirmation dialog before executing onClick. |
---
# ContextMenu
Context menus appear at the mouse position when the user right-clicks. Use the `openContextMenu` helper function to create and display a context menu with minimal code.
```tsx
import { openContextMenu, Menu, MenuItem } from "cx/widgets";
export default (
{
openContextMenu(
e,
,
instance
);
}}
>
Right-click here to open context menu
);
```
The `openContextMenu` function takes three arguments:
- The mouse event (to get the click position and prevent default behavior)
- The menu content (typically a `Menu` with `MenuItem` children)
- The instance (accessed via `e.instance`) — this allows the menu to access the store and controller methods
## Grid Context Menus
Context menus are commonly used with grids for row and column actions. Use `onRowContextMenu` for row menus and `onContextMenu` on column definitions for header menus.
```tsx
import { createModel } from "cx/data";
import { openContextMenu, Menu, MenuItem, Grid } from "cx/widgets";
interface PageModel {
records: { id: number; name: string; value: number }[];
}
const m = createModel();
export default (
{
openContextMenu(
e,
,
instance,
);
}}
onRowContextMenu={(e, instance) => {
openContextMenu(
e,
,
instance,
);
}}
/>
);
```
Use `` elements to add visual separators between menu groups.
## openContextMenu
```ts
openContextMenu(
event: MouseEvent,
content: any,
instance?: Store | Instance,
options?: object
): Promise
```
| Argument | Description |
| -------- | ----------- |
| `event` | The mouse event from `onContextMenu`. Used to position the menu and prevent default browser context menu. |
| `content` | Menu content — typically a `Menu` with `MenuItem` children. |
| `instance` | Instance for data binding and controller access. Access via `e.instance`. |
| `options` | Optional configuration passed to the dropdown. |
## Configuration
`ContextMenu` extends `Dropdown` with these defaults:
| Property | Default | Description |
| -------- | ------- | ----------- |
| `trackMouse` | `true` | Position menu at cursor location. |
| `dismissOnFocusOut` | `true` | Close when focus leaves the menu. |
| `autoFocus` | `true` | Focus the menu when opened. |
| `placementOrder` | `"down-right right up-right..."` | Preferred placement directions. |
---
# TextField
The `TextField` widget is used for single-line text inputs. It's one of the most commonly used form controls with support for validation, placeholders, icons, and various input modes.
```tsx
import { createModel } from "cx/data";
import { bind, LabelsTopLayout } from "cx/ui";
import { TextField } from "cx/widgets";
interface Model {
text: string;
placeholder: string;
}
const m = createModel();
export default (
);
```
## Styling
TextField supports icons, clear buttons, and custom styling for the input element.
```tsx
import { createModel } from "cx/data";
import { bind, expr, LabelsTopLayout } from "cx/ui";
import { TextField } from "cx/widgets";
import "../../icons/lucide";
interface Model {
text: string;
search: string;
password: string;
showPassword: boolean;
}
const m = createModel();
export default (
(show ? "text" : "password"))}
icon={{
name: expr(m.showPassword, (show) => (show ? "eye-off" : "eye")),
style: "pointer-events: all; cursor: pointer",
onClick: (e, { store }) => {
e.stopPropagation();
store.toggle(m.showPassword);
},
}}
/>
);
```
## See Also
- [Forwarding Keyboard Inputs](/docs/forms/list-forwarding-keyboard-inputs) - Forward keyboard events from TextField to a List
## Configuration
### Core Properties
| Property | Type | Default | Description |
| ------------- | --------- | --------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `value` | `string` | `null` | Text value of the input |
| `placeholder` | `string` | | Hint text displayed when the field is empty |
| `disabled` | `boolean` | `false` | Disables the input |
| `enabled` | `boolean` | | Opposite of `disabled` |
| `readOnly` | `boolean` | `false` | Makes the input read-only |
| `required` | `boolean` | `false` | Marks the field as required |
| `label` | `string` | | Field label text |
| `viewMode` | `boolean` | `false` | Display as plain text instead of interactive input |
| `emptyText` | `string` | | Text shown in view mode when the field is empty |
| `emptyValue` | `any` | `null` | Value written to the store when the field is cleared |
| `inputType` | `string` | `"text"` | HTML input type. Use `"password"` for password fields |
| `reactOn` | `string` | `"change input blur enter"` | Events that trigger value updates. Options: `"input"`, `"change"`, `"enter"`, `"blur"`. Separate with space |
### Validation
| Property | Type | Default | Description |
| ------------------------------ | --------- | ---------------------------------------- | --------------------------------------------------------- |
| `minLength` | `number` | | Minimum allowed text length |
| `maxLength` | `number` | | Maximum allowed text length |
| `minLengthValidationErrorText` | `string` | `"Enter {[{0}-{1}]} more character(s)."` | Error message when text is too short |
| `maxLengthValidationErrorText` | `string` | `"Use {0} characters or fewer."` | Error message when text is too long |
| `validationRegExp` | `RegExp` | | Regular expression for validating the input |
| `validationErrorText` | `string` | `"The entered value is not valid."` | Error message when regex validation fails |
| `trim` | `boolean` | `false` | Trims whitespace before storing and validating |
| `error` | `string` | | Custom error message. Field is marked invalid if set |
| `visited` | `boolean` | | If `true`, shows validation errors immediately |
| `validationMode` | `string` | `"tooltip"` | How to show errors: `"tooltip"`, `"help"`, `"help-block"` |
| `validationParams` | `object` | | Additional params passed to `onValidate` |
### Appearance
| Property | Type | Default | Description |
| ----------------- | --------------- | ------- | ---------------------------------------------------------- |
| `icon` | `string/object` | | Icon on the left side. Can include `onClick` and `tooltip` |
| `showClear` | `boolean` | `false` | Shows a clear button when the field has a value |
| `hideClear` | `boolean` | `false` | Opposite of `showClear` |
| `alwaysShowClear` | `boolean` | `false` | Shows clear button even when field is required |
| `tooltip` | `string/object` | | Tooltip text or configuration |
| `inputStyle` | `string/object` | | CSS styles applied to the input element |
| `inputClass` | `string` | | CSS class applied to the input element |
| `inputAttrs` | `object` | | Additional HTML attributes for the input element |
| `asterisk` | `boolean` | `false` | Appends asterisk to label for required fields |
### Behavior
| Property | Type | Default | Description |
| ------------------ | --------- | ------- | --------------------------------------------------- |
| `autoFocus` | `boolean` | `false` | Automatically focuses the field on mount |
| `tabOnEnterKey` | `boolean` | `false` | Moves focus to the next field when Enter is pressed |
| `trackFocus` | `boolean` | `false` | Adds `cxs-focus` CSS class when input is focused |
| `focused` | `boolean` | | Bound value updated when field gains/loses focus |
| `tabIndex` | `string` | | Custom tab index |
| `keyboardShortcut` | `string` | | Keyboard shortcut to focus the field |
| `id` | `string` | | HTML id attribute for the input |
### Callbacks
| Property | Description |
| ------------ | ------------------------------------------------------------------------------------------------- |
| `onFocus` | `(e, instance) => void` - Called when field receives focus |
| `onBlur` | `(e, instance) => void` - Called when field loses focus |
| `onKeyDown` | `(e, instance) => boolean` - Return `false` to prevent default |
| `onValidate` | `(value, instance, validationParams) => string` - Return error message or `false` to clear errors |
---
# TextArea
The `TextArea` widget is used for multi-line text inputs. It extends `TextField` and inherits most of its properties.
```tsx
import { createModel } from "cx/data";
import { bind, LabelsTopLayout } from "cx/ui";
import { TextArea } from "cx/widgets";
interface Model {
text: string;
placeholder: string;
}
const m = createModel();
export default (
);
```
## Configuration
### Core Properties
| Property | Type | Default | Description |
| ------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------- |
| `value` | `string` | `null` | Text value of the input |
| `rows` | `number` | | Number of visible text rows |
| `placeholder` | `string` | | Hint text displayed when the field is empty |
| `disabled` | `boolean` | `false` | Disables the input |
| `enabled` | `boolean` | | Opposite of `disabled` |
| `readOnly` | `boolean` | `false` | Makes the input read-only |
| `required` | `boolean` | `false` | Marks the field as required |
| `label` | `string` | | Field label text |
| `viewMode` | `boolean` | `false` | Display as plain text instead of interactive input |
| `emptyText` | `string` | | Text shown in view mode when the field is empty |
| `reactOn` | `string` | `"blur"` | Events that trigger value updates. Options: `"input"`, `"blur"`. Use `"input"` for real-time updates |
### Validation
| Property | Type | Default | Description |
| ------------------------------ | ---------- | ---------------------------------------- | --------------------------------------------------------- |
| `minLength` | `number` | | Minimum allowed text length |
| `maxLength` | `number` | | Maximum allowed text length |
| `minLengthValidationErrorText` | `string` | `"Enter {[{0}-{1}]} more character(s)."` | Error message when text is too short |
| `maxLengthValidationErrorText` | `string` | `"Use {0} characters or fewer."` | Error message when text is too long |
| `validationRegExp` | `RegExp` | | Regular expression for validating the input |
| `validationErrorText` | `string` | `"The entered value is not valid."` | Error message when regex validation fails |
| `trim` | `boolean` | `false` | Trims whitespace before storing and validating |
| `error` | `string` | | Custom error message. Field is marked invalid if set |
| `visited` | `boolean` | | If `true`, shows validation errors immediately |
| `validationMode` | `string` | `"tooltip"` | How to show errors: `"tooltip"`, `"help"`, `"help-block"` |
### Appearance
| Property | Type | Default | Description |
| ------------ | --------------- | ------- | ------------------------------------------ |
| `tooltip` | `string/object` | | Tooltip text or configuration |
| `inputStyle` | `string/object` | | CSS styles applied to the textarea element |
| `inputClass` | `string` | | CSS class applied to the textarea element |
### Behavior
| Property | Type | Default | Description |
| ------------ | --------- | -------- | -------------------------------------------------- |
| `autoFocus` | `boolean` | `false` | Automatically focuses the field on mount |
| `trackFocus` | `boolean` | `false` | Adds `cxs-focus` CSS class when input is focused |
| `focused` | `boolean` | | Bound value updated when field gains/loses focus |
| `tabIndex` | `string` | | Custom tab index |
| `id` | `string` | | HTML id attribute for the textarea |
### Callbacks
| Property | Type | Description |
| ------------ | ---------- | ------------------------------------------------------------------------------- |
| `onValidate` | `function` | `(value, instance, validationParams) => string|undefined` - Custom validation |
---
# NumberField
The `NumberField` widget is used for numeric inputs. It supports formatting for currencies, percentages, and decimal places, with built-in validation for min/max values.
```tsx
import { createModel } from "cx/data";
import { bind, LabelsTopLayout } from "cx/ui";
import { NumberField } from "cx/widgets";
interface Model {
number: number;
placeholder: number;
}
const m = createModel();
export default (
);
```
## Formatting
NumberField supports various number formats including currencies and percentages. Use `scale` to convert between display and stored values (e.g., 0.01 for percentages).
```tsx
import { createModel } from "cx/data";
import { bind, LabelsTopLayout } from "cx/ui";
import { NumberField } from "cx/widgets";
interface Model {
price: number;
percent: number;
quantity: number;
}
const m = createModel();
export default (
);
```
## Configuration
### Core Properties
| Property | Type | Default | Description |
| ------------- | --------- | -------- | ------------------------------------------------------------- |
| `value` | `number` | `null` | Numeric value |
| `format` | `string` | `"n"` | Display format. See [Formatting](/docs/intro/formatting) |
| `scale` | `number` | `1` | Multiplier for converting between stored and displayed value |
| `offset` | `number` | `0` | Offset added after scaling |
| `placeholder` | `string` | | Hint text displayed when the field is empty |
| `disabled` | `boolean` | `false` | Disables the input |
| `enabled` | `boolean` | | Opposite of `disabled` |
| `readOnly` | `boolean` | `false` | Makes the input read-only |
| `required` | `boolean` | `false` | Marks the field as required |
| `label` | `string` | | Field label text |
| `viewMode` | `boolean` | `false` | Display as plain text instead of interactive input |
| `emptyText` | `string` | | Text shown in view mode when the field is empty |
### Value Constraints
| Property | Type | Default | Description |
| -------------- | --------- | ------- | ---------------------------------------------------- |
| `minValue` | `number` | | Minimum allowed value |
| `minExclusive` | `boolean` | `false` | If `true`, the minimum value itself is not allowed |
| `maxValue` | `number` | | Maximum allowed value |
| `maxExclusive` | `boolean` | `false` | If `true`, the maximum value itself is not allowed |
| `constrain` | `boolean` | `false` | If `true`, automatically clamps value to min/max |
### Increment Behavior
| Property | Type | Default | Description |
| --------------------- | --------- | ------- | ----------------------------------------------------------------- |
| `increment` / `step` | `number` | | Step size for arrow keys and mouse wheel |
| `incrementPercentage` | `number` | `0.1` | Auto-calculated increment as percentage of current value |
| `snapToIncrement` | `boolean` | `true` | If `true`, values snap to increment multiples on wheel change |
Add `wheel` to `reactOn` to change values using the mouse wheel. When enabled, page scrolling is disabled while the field is focused.
### Validation
| Property | Type | Default | Description |
| ----------------------- | -------- | ------------------------------------ | --------------------------------------------------------- |
| `minValueErrorText` | `string` | `"Enter {0} or more."` | Error message when value is below minimum |
| `minExclusiveErrorText` | `string` | `"Enter a number greater than {0}."` | Error message when value equals exclusive minimum |
| `maxValueErrorText` | `string` | `"Enter {0} or less."` | Error message when value is above maximum |
| `maxExclusiveErrorText` | `string` | `"Enter a number less than {0}."` | Error message when value equals exclusive maximum |
| `inputErrorText` | `string` | `"Invalid number entered."` | Error message for invalid number input |
| `error` | `string` | | Custom error message. Field is marked invalid if set |
| `visited` | `boolean`| | If `true`, shows validation errors immediately |
| `validationMode` | `string` | `"tooltip"` | How to show errors: `"tooltip"`, `"help"`, `"help-block"` |
### Appearance
| Property | Type | Default | Description |
| ----------------- | --------------- | ------- | ----------------------------------------------- |
| `icon` | `string/object` | | Icon on the left side |
| `showClear` | `boolean` | `false` | Shows a clear button when the field has a value |
| `hideClear` | `boolean` | `false` | Opposite of `showClear` |
| `alwaysShowClear` | `boolean` | `false` | Shows clear button even when field is required |
| `tooltip` | `string/object` | | Tooltip text or configuration |
| `inputStyle` | `string/object` | | CSS styles applied to the input element |
| `inputClass` | `string` | | CSS class applied to the input element |
### Behavior
| Property | Type | Default | Description |
| -------------- | --------- | -------------- | --------------------------------------------------- |
| `autoFocus` | `boolean` | `false` | Automatically focuses the field on mount |
| `reactOn` | `string` | `"enter blur"` | Events that trigger value updates. Options: `"change"`, `"enter"`, `"blur"`, `"wheel"`. Separate with space |
| `trackFocus` | `boolean` | `false` | Adds `cxs-focus` CSS class when input is focused |
| `inputType` | `string` | `"text"` | HTML input type |
| `onParseInput` | `function`| | Custom parser for text input |
### Callbacks
| Property | Description |
| ------------ | ---------------------------------------------------------------------------------- |
| `onValidate` | `(value, instance, validationParams) => string` - Return error message to validate |
---
# Checkbox
The `Checkbox` widget is used for expressing binary choices. It supports both standard styled checkboxes and native HTML checkboxes.
```tsx
import { createModel } from "cx/data";
import { bind, LabelsTopLayout } from "cx/ui";
import { Checkbox } from "cx/widgets";
interface Model {
checked: boolean;
indeterminate: boolean;
}
const m = createModel();
export default (
);
```
## Three State Mode
Use the `indeterminate` prop to enable three-state mode. This displays a square icon when the value is `null` or `undefined`, which is useful for "select all" scenarios where only some items are selected.
## Configuration
### Core Properties
| Property | Type | Default | Description |
| --------------- | --------- | ------- | -------------------------------------------------------------- |
| `value` | `boolean` | `false` | Checked state of the checkbox |
| `checked` | `boolean` | | Alias for `value` |
| `text` | `string` | | Text displayed next to the checkbox |
| `label` | `string` | | Label displayed before the checkbox |
| `disabled` | `boolean` | `false` | Disables the checkbox |
| `readOnly` | `boolean` | `false` | Makes the checkbox read-only |
| `required` | `boolean` | `false` | Requires the checkbox to be checked |
| `indeterminate` | `boolean` | `false` | Enables three-state mode (checked, unchecked, indeterminate) |
| `viewMode` | `boolean` | `false` | Display as plain text instead of interactive checkbox |
| `autoFocus` | `boolean` | `false` | Automatically focus the field on mount |
### Appearance
| Property | Type | Default | Description |
| ------------ | --------- | ------- | -------------------------------------------------- |
| `native` | `boolean` | `false` | Use native HTML checkbox instead of styled version |
| `inputStyle` | `object` | | CSS styles for the checkbox element |
| `viewText` | `string` | | Text displayed in view mode |
| `emptyText` | `string` | | Text shown in view mode when value is empty |
| `tooltip` | `string` | | Tooltip text or configuration |
### Behavior
| Property | Type | Default | Description |
| ------------ | --------- | ------- | -------------------------------------------------------- |
| `unfocusable`| `boolean` | `false` | Disable focusing on the checkbox (useful in grids) |
### Callbacks
| Property | Type | Description |
| ------------ | ---------- | ----------------------------------------------------------------------------- |
| `onValidate` | `function` | Custom validation function returning error message or undefined |
---
# Radio
Radio buttons are used for making a single selection within a limited number of options. All radios in a group share the same `value` binding, and each has a unique `option`. When clicked, the radio's `option` value is written to `value`.
```tsx
import { createModel } from "cx/data";
import { bind } from "cx/ui";
import { Radio } from "cx/widgets";
interface Model {
size: string;
}
const m = createModel();
export default (
);
```
## Configuration
### Core Properties
| Property | Type | Default | Description |
| ---------- | --------- | ------- | ------------------------------------------------------------- |
| `value` | `any` | | Selected value shared by all radios in a group |
| `selection`| `any` | | Alias for `value` |
| `option` | `any` | | Value written to `value` when this radio is selected |
| `text` | `string` | | Text displayed next to the radio button |
| `label` | `string` | | Label displayed before the radio button |
| `disabled` | `boolean` | `false` | Disables the radio button |
| `readOnly` | `boolean` | `false` | Makes the radio button read-only |
| `required` | `boolean` | `false` | Marks the field as required |
| `default` | `boolean` | `false` | Auto-select this radio if `value` is undefined |
| `viewMode` | `boolean` | `false` | Display as plain text instead of interactive radio |
| `autoFocus`| `boolean` | `false` | Automatically focus the field on mount |
### Appearance
| Property | Type | Default | Description |
| ------------ | --------- | ------- | ---------------------------------------------------- |
| `native` | `boolean` | `false` | Use native HTML radio instead of styled version |
| `inputStyle` | `object` | | CSS styles for the radio element |
| `emptyText` | `string` | | Text shown in view mode when no option is selected |
| `tooltip` | `string` | | Tooltip text or configuration |
### Callbacks
| Property | Type | Description |
| ------------ | ---------- | ------------------------------------------------------- |
| `onValidate` | `function` | Custom validation function returning error or undefined |
---
# Switch
The `Switch` widget is a two-state toggle control. It offers the same functionality as a `Checkbox` but with a modern sliding appearance that's commonly used for on/off settings.
```tsx
import { createModel } from "cx/data";
import { bind, expr, LabelsTopLayout } from "cx/ui";
import { Switch } from "cx/widgets";
interface Model {
enabled: boolean;
}
const m = createModel();
export default (
(v ? "ON" : "OFF"))}
/>
Custom text
);
```
## Configuration
### Core Properties
| Property | Type | Default | Description |
| ---------- | --------- | ------- | ------------------------------------------------------ |
| `on` | `boolean` | `false` | State of the switch (true = on) |
| `off` | `boolean` | `true` | Inverse state (true = off). Alternative to `on` |
| `value` | `boolean` | | Alias for `on` |
| `text` | `string` | | Text displayed next to the switch |
| `label` | `string` | | Label displayed before the switch |
| `disabled` | `boolean` | `false` | Disables the switch |
| `readOnly` | `boolean` | `false` | Makes the switch read-only |
| `required` | `boolean` | `false` | Marks the field as required |
| `viewMode` | `boolean` | `false` | Display as plain text instead of interactive switch |
| `autoFocus`| `boolean` | `false` | Automatically focus the switch on mount |
### Appearance
| Property | Type | Default | Description |
| ------------------ | --------- | ------- | ------------------------------------------------- |
| `handleStyle` | `object` | | CSS styles for the switch handle (knob) |
| `rangeStyle` | `object` | | CSS styles for the track when switch is on |
| `emptyText` | `string` | | Text shown in view mode when value is empty |
| `tooltip` | `string` | | Tooltip text or configuration |
### Behavior
| Property | Type | Default | Description |
| ------------------ | --------- | ------- | ------------------------------------------------- |
| `focusOnMouseDown` | `boolean` | `false` | Whether to focus the switch on mouse down |
### Callbacks
| Property | Type | Description |
| ------------ | ---------- | ------------------------------------------------------- |
| `onValidate` | `function` | Custom validation function returning error or undefined |
---
# Validation
CxJS provides a comprehensive validation system for form fields. The main components are:
| Component | Description |
| --------- | ----------- |
| [ValidationGroup](validation-group) | Container that tracks validation state of all fields inside it. Propagates settings like `disabled`, `readOnly`, or `asterisk` to children |
| [Validator](validator) | Performs custom validation on computed or combined values. Use for cross-field or complex validation |
| [ValidationError](validation-error) | Displays validation error messages. Renders as a `