A modern React component library for building intelligent, action-driven UI experiences.
Chalo provides a declarative API for creating guided user experiences, interactive walkthroughs, and automated UI workflows. Built with Zustand for state management and Framer Motion for smooth animations.
- π― Declarative Missions - Define multi-step guided experiences with JSON-like configs
- π Action Execution Engine - Automate UI interactions (clicks, form fills, API calls, navigation)
- π¨ Smart Components - Pre-built
SmartDrawer,TargetHighlight, and more - π« Smooth Animations - Powered by Framer Motion with zero configuration
- π§ State Management - Lightweight Zustand store with full TypeScript support
- π Extensible Handlers - Register custom action handlers for any UI interaction
- π¦ Tree-Shakeable - Only bundle what you use
- π³ Dual ESM/CJS - Works with all modern bundlers and legacy setups
# npm
npm install @osiloke/chalo
# yarn
yarn add @osiloke/chalo
# pnpm
pnpm add @osiloke/chaloChalo requires the following packages (must be installed in your project):
{
"react": ">=18.0.0",
"react-dom": ">=18.0.0",
"zustand": ">=5.0.0",
"framer-motion": ">=12.0.0"
}import { useChalo, SmartDrawer, Mission } from 'chalo';
function App() {
const {
activeMissionId,
currentStepId,
nextStep,
prevStep,
completeMission
} = useChalo();
return (
<div>
{/* Your app content */}
<SmartDrawer />
</div>
);
}import { Mission, Step, Bubble } from 'chalo';
const onboardingMission: Mission = {
id: 'app-onboarding',
title: 'Welcome to the App! π',
description: 'Let us walk you through the main features.',
steps: [
{
id: 'welcome',
title: 'Getting Started',
content: 'This is the dashboard. Here you can manage your projects.',
targetElement: '#dashboard',
bubbles: [
{
id: 'continue-btn',
type: 'action-group',
actions: [
{ label: 'Next β', type: 'next' }
]
}
]
},
{
id: 'create-project',
title: 'Create Your First Project',
content: 'Click the button below to create a new project.',
targetElement: '#create-btn',
waitFor: {
type: 'field_value',
field: 'projectCreated',
value: true
},
actionSequence: [
{
id: 'click-create',
type: 'click',
config: { field: 'create-btn' }
}
]
}
],
allowCompletion: true
};import { useChalo } from 'chalo';
import { useEffect } from 'react';
function App() {
const { registerMission, startMission } = useChalo();
useEffect(() => {
registerMission(onboardingMission);
startMission('app-onboarding');
}, [registerMission, startMission]);
return <YourApp />;
}Main hook for accessing chalo state and actions.
Parameters:
interface UseChaloOptions {
debug?: boolean; // Enable debug logging (default: false)
}Returns:
interface ChaloActions {
// Mission Management
registerMission: (mission: Mission) => void;
startMission: (missionId: string) => void;
pauseMission: () => void;
resumeMission: () => void;
completeMission: () => void;
resetMission: () => void;
// Navigation
nextStep: () => void;
prevStep: () => void;
goToStep: (stepId: string) => void;
// Field Management
updateField: (name: string, value: any, status?: FieldStatus) => void;
// Action Engine
registerActionHandler: (type: string, handler: ActionHandler) => void;
executeAction: (action: Action) => Promise<ActionResult>;
executeActionSequence: (actions: Action[]) => Promise<Record<string, ActionResult>>;
cancelExecution: () => void;
// Tours & History
dismissAllTours: () => void;
recordTourEntry: (missionId: string, stepId: string, completed: boolean) => void;
// State
reset: () => void;
setError: (error: string | null) => void;
}Example:
const {
activeMissionId,
currentStepId,
missionProgress,
fieldValues,
nextStep,
prevStep,
updateField
} = useChalo({ debug: true });A context-aware drawer component for displaying mission steps.
<SmartDrawer
position="right"
width="400px"
renderBubble={(bubble) => <CustomBubble {...bubble} />}
/>Props:
| Prop | Type | Default | Description |
|---|---|---|---|
position |
'left' | 'right' | 'top' | 'bottom' |
'right' |
Drawer position |
width |
string |
'360px' |
Drawer width |
renderBubble |
(bubble: Bubble) => ReactNode |
- | Custom bubble renderer |
onOpen |
() => void |
- | Open callback |
onClose |
() => void |
- | Close callback |
Highlights target elements with animated overlays.
<TargetHighlight
selector="#my-element"
label="Click here to continue"
pulse
/>Props:
| Prop | Type | Default | Description |
|---|---|---|---|
selector |
string |
required | CSS selector for target |
label |
string |
- | Tooltip text |
pulse |
boolean |
false |
Enable pulse animation |
onClick |
() => void |
- | Click handler |
style |
CSSProperties |
- | Custom styles |
Defines a complete guided experience.
interface Mission {
id: string;
title: string;
description?: string;
steps: Step[];
metadata?: Record<string, unknown>;
onComplete?: () => void;
allowCompletion?: boolean;
actions?: Action[];
}A single step within a mission.
interface Step {
id: string;
title: string;
content: string | ReactNode;
bubbles?: Bubble[];
targetField?: string;
targetElement?: string;
successCondition?: SuccessCondition;
waitFor?: SuccessCondition;
condition?: SuccessCondition;
navigationRules?: {
canGoBack?: boolean;
canSkip?: boolean;
};
actions?: StepAction[];
actionSequence?: Action[];
}Atomic UI interaction for the execution engine.
interface Action {
id: string;
type: 'click' | 'scroll' | 'fill_field' | 'api_call' | 'wait' | 'conditional' | 'navigate' | 'custom';
config: ActionConfig;
label?: string;
retry?: RetryConfig;
rollback?: RollbackConfig;
dependsOn?: string[];
condition?: SuccessCondition;
}π‘ See the TypeScript Definitions for complete type documentation.
Extend the engine with your own action types:
import { useChalo, ActionHandler } from 'chalo';
function App() {
const { registerActionHandler } = useChalo();
useEffect(() => {
const showToast: ActionHandler = async (config, context) => {
const { message, duration } = config;
toast.show(message, { duration });
return { success: true };
};
registerActionHandler('show_toast', showToast);
}, [registerActionHandler]);
}Make steps dynamic based on user state:
{
id: 'advanced-feature',
title: 'Advanced Features',
content: 'Check out these powerful tools!',
condition: {
type: 'custom',
predicate: (value, formState) => formState.isPremiumUser
},
// This step only runs if the user is premium
}Chalo automatically tracks tour progress:
const { tourHistory, recordTourEntry } = useChalo();
// Check if user completed a tour
if (tourHistory['onboarding']?.completed) {
// Skip onboarding next time
}packages/chalo/
βββ src/
β βββ components/ # UI components
β β βββ SmartDrawer.tsx
β β βββ TargetHighlight.tsx
β βββ engine/ # Action execution engines
β β βββ action-engine.ts
β β βββ index.ts
β βββ hooks/ # React hooks
β β βββ use-chalo.ts
β βββ store.ts # Zustand store
β βββ types.ts # TypeScript definitions
β βββ index.ts # Public API
βββ package.json
βββ tsconfig.json
βββ vite.config.ts
This package is part of a pnpm monorepo:
# Install dependencies
pnpm install
# Build the library
pnpm run build:chalo
# Run tests
pnpm test
# Run tests with UI
pnpm run test:ui
# Lint code
pnpm run lint
# Format code
pnpm run format:fix
# Verify build
./scripts/verify-build.shReleases are fully automated via semantic-release. Every push to main is analyzed for conventional commits, and if there are release-worthy changes, a new version is automatically:
- Version-bumped (semver based on commit type)
- Changelog-updated
- Published to npm with provenance
- Released on GitHub with auto-generated notes
Use these commit prefixes to trigger releases:
| Prefix | Example | Effect |
|---|---|---|
feat: |
feat: add TargetHighlight component |
Bumps minor version |
fix: |
fix: resolve drawer positioning bug |
Bumps patch version |
BREAKING CHANGE: in body |
feat: redesign API\n\nBREAKING CHANGE: ... |
Bumps major version |
docs:, chore:, ci: |
docs: update README |
No release |
npx semantic-release --dry-runπ See GitHub Actions Workflow for details.
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests (
pnpm test) - Lint & format (
pnpm run lint && pnpm run format:fix) - Commit using Conventional Commits
- Push and open a PR
- Use TypeScript strictly (no
anyunless absolutely necessary) - Follow existing code style (enforced by ESLint + Prettier)
- Write tests for new features
- Update documentation for API changes
This project follows Semantic Versioning:
- MAJOR - Breaking changes
- MINOR - New features (backwards compatible)
- PATCH - Bug fixes
See CHANGELOG.md for release history.
| Issue | Workaround | Status |
|---|---|---|
| SSR not yet supported | Use dynamic imports with ssr: false |
π§ Planned |
| React 17 compatibility | Use React 18+ |
Found a bug? Open an issue!
MIT Β© Osiloke Harold Emoekpere
Built with:
- React - UI library
- Zustand - State management
- Framer Motion - Animations
- Vite - Build tool
- TypeScript - Type safety
