sangam-ui
v0.1.11
Published
ESDS Sangam is a design system for the ESDS platform. It provides a set of components and utilities to help you build your UI.
Maintainers
Readme
The ESDS Sangam Design System is the unified UI library for the ESDS platform. This package, sangam-ui, provides typed, accessible React components built on top of shared icons, design tokens, and global styles, so product teams can ship consistent and high‑quality interfaces quickly.
This README is written for developers who will install and use the design system in their applications.
1. Package Overview
sangam-ui is the UI components package of the Sangam Design System. It exposes reusable primitives, components, patterns, hooks, and types that are:
- Themed using Sangam tokens and styles.
- Accessible by default (ARIA-friendly, keyboard navigable).
- Composable and type-safe for modern React apps.
The design system is split into multiple npm packages so you can adopt only what you need while sharing a single source of truth for the visual language.
2. Packages Included
| Layer | Package name | Description |
| ------ | --------------------- | -------------------------------------------------------------------------- |
| Icons | @esds-sangam/icons | SVG-based icon components and icon utilities for React. |
| Tokens | @esds-sangam/tokens | Design tokens for colors, spacing, typography, radius, shadows, and more. |
| Styles | @esds-sangam/styles | Global CSS, theme variables (light/dark), and Tailwind integration styles. |
| UI | sangam-ui | React UI primitives and components built on top of tokens and styles. |
You are currently viewing the README for the UI package: sangam-ui.
3. Installation
Install the full stack (recommended for most app teams):
npm
npm install sangam-uiyarn
yarn add sangam-uipnpm
pnpm add sangam-uiYou can also install packages individually if you only need a subset, for example:
npm install @esds-sangam/icons4. Peer Dependencies
The design system targets modern React applications.
| Package | Version (minimum) |
| ----------- | ----------------- |
| react | ^18.0.0 |
| react-dom | ^18.0.0 |
Ensure your application satisfies these peer dependencies to avoid warnings or runtime issues.
5. Initial Setup (Vite + React + TypeScript)
Follow these steps after React + TypeSCript Project Setup to wire sangam-ui into project.
5.1 Configure PostCSS — order matters
[!IMPORTANT] At your project root, create a
postcss.config.cjsfile. Thepostcss-importplugin must come first — if the order is wrong, Tailwind will error with "@import must precede all other statements" or "@layer base used but no matching @tailwind base directive".
// postcss.config.cjs
module.exports = {
plugins: {
"postcss-import": {}, // MUST be first: inlines @import before Tailwind sees the file
tailwindcss: {},
autoprefixer: {},
},
};5.2 Configure Tailwind — use the Sangam preset
[!IMPORTANT] At your project root, create a
tailwind.config.cjsfile. Always spread...sangamTailwindand extend itscontentarray — never replace it. Replacing it strips the Sangam theme (colors, spacing, radii, shadows, darkMode) and component class paths, causing broken styles.
// tailwind.config.cjs
const sangamTailwind = require("@esds-sangam/tailwind-config");
/** @type {import('tailwindcss').Config} */
module.exports = {
...sangamTailwind,
content: [
...sangamTailwind.content,
"./index.html",
"./src/**/*.{ts,tsx}",
"./node_modules/sangam-ui/dist/**/*.{js,mjs}",
],
};Spreading ...sangamTailwind gives you the Sangam theme (colors, spacing, radii, shadows), darkMode setting, and any plugins — without having to duplicate them.
Base44: using .js config files
If your project (e.g. Base44) expects postcss.config.js and tailwind.config.js instead of .cjs, use the same content with the .js extension:
postcss.config.js
// postcss.config.js
export default {
plugins: {
"postcss-import": {}, // MUST be first: inlines @import before Tailwind sees the file
tailwindcss: {},
autoprefixer: {},
},
};tailwind.config.js
// tailwind.config.js
import sangamTailwind from "@esds-sangam/tailwind-config";
/** @type {import('tailwindcss').Config} */
export default {
...sangamTailwind,
content: [
...sangamTailwind.content,
"./index.html",
"./src/**/*.{ts,tsx}",
"./node_modules/sangam-ui/dist/**/*.{js,mjs}",
],
};5.3 Wire styles — single CSS entry file
[!IMPORTANT] Use one main CSS file as the Tailwind entry (e.g.
src/index.css). The@importmust appear before the@tailwinddirectives:
/* src/index.css */
@import "@esds-sangam/styles/tailwind.css"; /* pulls in globals, theme, and Tailwind layers */
@tailwind base;
@tailwind components;
@tailwind utilities;Common mistakes
- Putting
@tailwindbefore@import→ build error: "@import must precede all other statements" - Importing individual Sangam CSS files (e.g.
theme.css,dark.css) directly inmain.tsx→ each file is a separate PostCSS pass, causing "@layer base used but no matching @tailwind base directive" errors
5.4 App entry — import only your CSS file
In src/main.tsx, import only your single CSS file. Do not import Sangam CSS files directly here:
// src/main.tsx
import "./index.css"; // ← only this; Sangam styles are pulled in via @import inside index.css
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);5.5 Dark theme (optional)
To support dark mode, add the dark theme import inside src/index.css:
/* src/index.css */
@import "@esds-sangam/styles/tailwind.css";
@import "@esds-sangam/styles/dark.css"; /* add this line */
@tailwind base;
@tailwind components;
@tailwind utilities;Toggle dark mode at runtime by adding or removing the dark class on <html>:
document.documentElement.classList.toggle("dark");5.6 Setup checklist
| Step | Action |
| ---- | ------------------------------------------------------------------------------------------------------------------------- |
| 1 | Install sangam-ui, @esds-sangam/styles, @esds-sangam/tokens, @esds-sangam/icons |
| 2 | Install tailwindcss, postcss, autoprefixer, postcss-import as dev dependencies |
| 3 | In postcss.config.cjs — put postcss-import first, then tailwindcss, then autoprefixer |
| 4 | In tailwind.config.cjs — spread sangamTailwind and extend content to include src/**/* and esds-sangam/dist/**/* |
| 5 | In src/index.css — @import "@esds-sangam/styles/tailwind.css" first, then @tailwind directives |
| 6 | In src/main.tsx — only import "./index.css", no direct Sangam CSS imports |
6. Usage Guide
6.1 Quick Start
Once setup is complete, import and use Sangam components anywhere in your app:
import { Button, Input, Badge } from "sangam-ui";
export function App() {
return (
<main className="p-6 space-y-4">
<Input label="Project name" placeholder="my-project" />
<div className="flex items-center gap-3">
<Button variant="primary" size="big">
Deploy
</Button>
<Badge size="small" variant="pill" state="success">
Live
</Badge>
</div>
</main>
);
}6.2 Full app example
// src/App.tsx
import {
Button,
Card,
Input,
Dropdown,
SearchField,
Checkbox,
Badge,
Toast,
Loader,
Skeleton,
Toggle,
} from "sangam-ui";
function App() {
return (
<main className="mx-auto flex max-w-3xl flex-col gap-6 p-6">
<header className="flex items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-semibold">ESDS Console</h1>
<p className="text-sm text-neutral-600">Manage resources across your ESDS projects.</p>
</div>
<Button variant="primary" size="big">
Create resource
</Button>
</header>
<Card className="space-y-4">
<Input label="Filter by name" placeholder="production-api" />
<div className="flex items-center justify-between gap-4">
<Dropdown
label="Status"
placeholder="All statuses"
options={[
{ value: "running", label: "Running" },
{ value: "stopped", label: "Stopped" },
]}
/>
<SearchField placeholder="Search resources" />
</div>
</Card>
</main>
);
}7. Component Examples
7.1 Button
import { Button } from "sangam-ui";
import { ChevronDown, Close } from "@esds-sangam/icons";
export function ButtonExamples() {
return (
<div className="flex flex-wrap gap-4">
<Button variant="primary" size="big">
Primary action
</Button>
<Button variant="secondary" size="big">
Secondary
</Button>
<Button variant="danger" size="big" icon={<Close />} leadingIcon>
Delete
</Button>
<Button variant="link" size="small" icon={<ChevronDown />} trailingIcon>
View more
</Button>
</div>
);
}7.2 Input
import { useState } from "react";
import { Input } from "sangam-ui";
export function InputExample() {
const [value, setValue] = useState("");
return (
<div className="space-y-4 max-w-sm">
<Input
label="Project name"
required
placeholder="Enter a project name"
helperText="This will be visible across your ESDS account."
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<Input
label="API key"
type="password"
placeholder="••••••••••"
error={value.length > 0 && value.length < 8}
helperText={
value.length > 0 && value.length < 8
? "API key must be at least 8 characters."
: "Keep this key secret."
}
/>
</div>
);
}7.3 Dropdown
import { useState } from "react";
import { Dropdown } from "sangam-ui";
const options = [
{ value: "prod", label: "Production" },
{ value: "staging", label: "Staging" },
{ value: "dev", label: "Development" },
];
export function DropdownExample() {
const [value, setValue] = useState<string | null>(null);
return (
<Dropdown
label="Environment"
placeholder="Select environment"
required
options={options}
value={value}
onChange={setValue}
helperText="This environment will be used for the next deployment."
showHelperIcon
/>
);
}7.4 SearchField
import { useEffect, useState } from "react";
import { SearchField, Loader } from "sangam-ui";
export function SearchFieldExample() {
const [query, setQuery] = useState("");
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (!query) return;
setIsLoading(true);
const timeout = setTimeout(() => setIsLoading(false), 600);
return () => clearTimeout(timeout);
}, [query]);
return (
<div className="space-y-3 max-w-md">
<SearchField
placeholder="Search resources"
value={query}
onChange={(e) => setQuery(e.target.value)}
showClearButton
/>
<div className="flex items-center gap-2 text-sm text-neutral-600">
{isLoading && <Loader size="small" aria-label="Searching" />}
<span>{isLoading ? "Searching…" : "Type to search across all resources."}</span>
</div>
</div>
);
}7.5 Checkbox
import { useState } from "react";
import { Checkbox } from "sangam-ui";
export function CheckboxExample() {
const [marketing, setMarketing] = useState(false);
const [terms, setTerms] = useState(false);
return (
<div className="space-y-3">
<Checkbox
title="Email updates"
subtext="Product tips, release notes, and marketing messages."
checked={marketing}
onCheckedChange={setMarketing}
/>
<Checkbox
title="I agree to the Terms of Service"
checked={terms}
onCheckedChange={setTerms}
/>
</div>
);
}7.6 Badge
import { Badge } from "sangam-ui";
export function BadgeExample() {
return (
<div className="flex flex-wrap items-center gap-3">
<Badge size="small" variant="pill" state="info">
New
</Badge>
<Badge size="small" variant="rounded" state="warning" isSolid>
Beta
</Badge>
<Badge size="small" variant="notification" state="error" isSolid>
3
</Badge>
</div>
);
}7.7 Toast
import { useState } from "react";
import { Button, Toast } from "sangam-ui";
export function ToastExample() {
const [visible, setVisible] = useState(false);
function handleSave() {
setVisible(true);
setTimeout(() => setVisible(false), 2500);
}
return (
<div className="space-y-4">
<Button variant="primary" onClick={handleSave}>
Save settings
</Button>
{visible && (
<div className="fixed bottom-6 right-6 z-50">
<Toast
variant="success"
title="Settings saved"
description="Your changes have been applied."
showClose
/>
</div>
)}
</div>
);
}7.8 Loader
import { useState } from "react";
import { Button, Loader, Card } from "sangam-ui";
export function LoaderExample() {
const [isLoading, setIsLoading] = useState(false);
function handleClick() {
setIsLoading(true);
setTimeout(() => setIsLoading(false), 1500);
}
return (
<div className="space-y-6 max-w-md">
<Card className="flex items-center justify-center gap-3 py-8">
{isLoading ? (
<>
<Loader size="small" aria-label="Loading data" />
<span className="text-sm text-neutral-700">Fetching latest metrics…</span>
</>
) : (
<span className="text-sm text-neutral-700">Metrics are up to date.</span>
)}
</Card>
<Button variant="primary" size="big" onClick={handleClick} disabled={isLoading}>
{isLoading ? "Refreshing…" : "Refresh data"}
</Button>
</div>
);
}7.9 Skeleton
import { useState, useEffect } from "react";
import { Card, Skeleton } from "sangam-ui";
export function SkeletonExample() {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => setLoaded(true), 1200);
return () => clearTimeout(timeout);
}, []);
return (
<Card className="w-full max-w-md p-4 space-y-3">
{loaded ? (
<>
<h2 className="text-base font-semibold">Usage by region</h2>
<p className="text-sm text-neutral-600">
Your workload is evenly distributed across all configured regions.
</p>
</>
) : (
<>
<Skeleton className="h-5 w-1/2" />
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-5/6" />
</>
)}
</Card>
);
}7.10 Toggle
import { useState } from "react";
import { Toggle } from "sangam-ui";
export function ToggleExample() {
const [autoscale, setAutoscale] = useState(true);
const [alerts, setAlerts] = useState(false);
return (
<div className="space-y-4">
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-sm font-medium text-neutral-900">Autoscale</p>
<p className="text-xs text-neutral-600">
Automatically scale instances up and down based on load.
</p>
</div>
<Toggle size="big" checked={autoscale} onCheckedChange={setAutoscale} />
</div>
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-sm font-medium text-neutral-900">Email alerts</p>
<p className="text-xs text-neutral-600">
Get notified when usage crosses configured thresholds.
</p>
</div>
<Toggle size="small" checked={alerts} onCheckedChange={setAlerts} />
</div>
</div>
);
}8. License
This project is licensed under the MIT License.
See the LICENSE file at the repository root for the full license text.
