<h1 align="center">@zendir/ui</h1>

<p align="center">
  React components for space operations.<br />
  Built on the <a href="https://www.astrouxds.com/">Astro UX Design System</a>.
</p>

<p align="center">
  <img src="https://img.shields.io/npm/v/@zendir/ui?style=flat&colorA=18181B&colorB=6C47FF" alt="npm version" />
  <img src="https://img.shields.io/badge/license-MIT-6C47FF.svg?style=flat&colorA=18181B" alt="License" />
  <img src="https://img.shields.io/badge/TypeScript-5.0+-6C47FF.svg?style=flat&colorA=18181B" alt="TypeScript" />
  <img src="https://img.shields.io/badge/React-18%2F19-6C47FF.svg?style=flat&colorA=18181B" alt="React" />
  <img src="https://img.shields.io/badge/WCAG_2.2_AA-conformant-6C47FF.svg?style=flat&colorA=18181B" alt="WCAG 2.2 AA conformant" />
</p>

<p align="center">
  <a href="#quick-start">Quick Start</a> · <a href="#components">Components</a> · <a href="#ai-integration">AI Integration</a> · <a href="#theme-system">Theming</a> · <a href="#accessibility">Accessibility</a> · <a href="https://storybook.zendir.io">Storybook</a> · <a href="CONTRIBUTING.md">Contributing</a> · <a href="CODE_OF_CONDUCT.md">Code of Conduct</a>
</p>

---

## Overview

React components for satellite telemetry, orbit visualisation, mission control dashboards, and space simulations. Follows the [Astro UX Design System](https://www.astrouxds.com/) used by the U.S. Space Force.

- **Astro UX-aligned** — dual-coded status indicators, classification banners, mission clocks
- **TypeScript-first** — full type definitions
- **Accessible** — [WCAG 2.2 AA](https://www.w3.org/TR/WCAG22/) conformant, keyboard navigable, reduced-motion aware, EAA-aligned
- **Tree-shakeable** with optional peer dependencies
- Works standalone or with [`zendir-ts`](https://www.npmjs.com/package/zendir-ts) for live API integration

The full component catalogue with live examples lives in [Storybook](https://storybook.zendir.io).

## Quick Start

### Install

```bash
npm install @zendir/ui
```

> The SDK is optional. All TypeScript types ship with the UI library.
> Install `zendir-ts` only if you need the API client.
>
> Recent versions of `@zendir/ui` hide the SDK specifier from static
> import-analysis, so no Vite alias is required — missing `zendir-ts` simply
> causes hooks like `useZendirSession` to report "SDK not installed" while the
> rest of the UI keeps working.
>
> **Older bundlers / strict resolvers:** if you hit "Failed to resolve import
> 'zendir-ts'", point the optional peer at the bundled stub:
> ```js
> // vite.config.js
> resolve: { alias: { 'zendir-ts': require.resolve('@zendir/ui/sdk-stub') } }
> ```

### Use

```tsx
import { ThemeProvider, SpacecraftCard, StatusIndicator } from '@zendir/ui/react';

function App() {
  return (
    <ThemeProvider defaultVariant="hybrid" defaultMode="dark">
      <StatusIndicator status="normal" label="System Healthy" />

      <SpacecraftCard
        spacecraft={{
          id: 'SAT-001',
          name: 'Explorer-1',
          noradId: 25544,
          type: 'LEO Satellite',
          status: 'operational',
        }}
        position={{
          latitude: 45.2,
          longitude: -122.5,
          altitude: 408,
          velocity: 7.66,
        }}
      />
    </ThemeProvider>
  );
}
```

---

## Components

### Core

Foundational primitives that compose into any interface.

| Component | Description |
|-----------|-------------|
| `Button` | Primary, secondary, and borderless variants with icon and loading support |
| `Input` | Text input with label, icon, validation, and size variants |
| `Select` | Dropdown selection with search and multi-select |
| `Toggle` | On/off switch with label |
| `Checkbox` | Checkbox and radio with accessible labeling |
| `Dialog` | Modal dialog with action slots |
| `Tabs` | Tabbed navigation with panel content |
| `Tooltip` | Contextual overlay on hover/focus |
| `Container` | Surface card with title, status accent, and elevation |
| `Badge` | Inline status label |
| `Pagination` | Page navigation controls |
| `NumberInput` | Numeric stepper with min/max/step and slider |
| `SidePanel` | Slide-out panel with configurable position |
| `DataTable` | Sortable, filterable data grid |
| `Menu` / `Popover` | Context menus and floating content |
| `ChatPanel` | AI chat interface with structured blocks, status system, and [4 LLM integration strategies](#ai-integration) |

### Status & Monitoring

Astro UX status system — six severity levels, dual-coded with color and shape so that status is never conveyed by color alone (WCAG 1.4.1 Use of Color).

```tsx
import { StatusIndicator, MonitoringIcon } from '@zendir/ui/react';

<StatusIndicator status="normal" label="Operational" />
<StatusIndicator status="caution" label="Battery Low" />
<StatusIndicator status="critical" label="Comm Loss" pulse />

<MonitoringIcon status="normal" icon="power" label="Power" sublabel="85%" />
```

**Status levels:** `off` · `standby` · `normal` · `caution` · `serious` · `critical`

### Data Cards

Pre-built cards for common space operations data. All cards support **compact mode** — collapsed by default, expand on hover, pin on click.

| Card | Data |
|------|------|
| `SpacecraftCard` | ID, orbit type, status, position |
| `TelemetryCard` | Subsystem health, alerts |
| `TelemetryStreamCard` | Live metric streaming |
| `AccessCard` | Ground station contact windows |
| `OrbitCard` | Keplerian elements, period, epoch |

```tsx
<SpacecraftCard compact spacecraft={data} position={pos} />
<TelemetryCard compact telemetry={tlm} />
```

### DataValue

Display telemetry values with automatic icons, units, and status derivation from 30+ built-in property presets.

```tsx
import { DataValue, DataValueGroup } from '@zendir/ui/react';

<DataValue property="temperature" value={72.5} />
<DataValue property="battery" value={25} />       {/* auto-derives 'caution' */}

<DataValueGroup columns={2} title="Power" icon="propulsion-power">
  <DataValue property="battery" value={85} />
  <DataValue property="voltage" value={28.4} />
  <DataValue property="solarPower" value={124} />
</DataValueGroup>
```

**Presets:** `battery` · `voltage` · `temperature` · `signalStrength` · `altitude` · `velocity` · `fuelLevel` · `cpuUsage` · `memoryUsage` · and 20+ more (see [Storybook](https://storybook.zendir.io) for the full list)

### Visualizations

Domain-specific visualization cards for mission operations.

| Card | Description |
|------|-------------|
| `LinkBudgetCard` | RF link budget waterfall |
| `ThermalHeatmapCard` | Spacecraft thermal zone status |
| `PropulsionCard` | Fuel, thrusters, delta-V budget |
| `EclipseTimerCard` | Eclipse/sunlight timing |
| `NavBallCard` | 3D attitude indicator |
| `SensorFootprintCard` | Ground sensor coverage |

### Charts

40+ chart types built on ECharts with Zendir brand and Astro UX data-viz palettes, built-in export (PNG, SVG, CSV), zoom/pan, and streaming support.

```tsx
import { PowerChart, AttitudeChart, GroundTrackMap } from '@zendir/ui/react';

<PowerChart data={powerData} height={300} />
<AttitudeChart data={attitudeData} height={250} />
<GroundTrackMap groundTrack={track} groundStations={stations} />
```

**Includes:** Line, Area, Bar, Pie, Donut, Gauge, Radar, Heatmap, Scatter, Sankey, Treemap, Sunburst, Candlestick, 3D Scatter/Surface/Bar, Waterfall, Doppler, and domain-specific charts for power, attitude, thermal, orbits, contacts, spectrum, and more.

### 3D Viewers

Three viewers covering the operations spectrum from full-Earth to full solar system, with optional WebGL2 (Three.js) and Cesium backends.

```tsx
import { ZenSpace3D, EarthViewer, SolarSystemViewer } from '@zendir/ui/react';

// Unified 3D space view — Three.js by default, optional Cesium backend for
// physically-accurate ECI/ECEF transforms and high-fidelity Earth imagery.
// Pair with `useSimulationScene` (see SDK Integration) to render a live
// simulation directly from a `zendir-ts` `ZendirClient`.
<ZenSpace3D
  satellites={satellites}
  groundStations={groundStations}
  satelliteCoverages={coverages}
  referenceTime={referenceDate}
/>

// Lightweight Three.js Earth view — good for small dashboards and demos.
<EarthViewer
  spacecraft={[{ id: 'SAT-001', name: 'Explorer-1', latitude: 45.2, longitude: -122.5, altitude: 408 }]}
  groundStations={[{ id: 'DSN-14', name: 'Goldstone', latitude: 35.4, longitude: -116.9 }]}
  showAtmosphere
  showOrbits
  autoRotate
/>

<SolarSystemViewer focusedPlanet="earth" autoOrbit showLabels />
```

Coverage geometry (footprint polygons, sensor cones, nadir lines) is rendered from explicit `SatelliteCoverage` records — see `ZenSpace3DTypes` for the full prop surface. Heavy orbital propagation (SGP4 / TLE) is intentionally **not** shipped in this package; bring your own propagator (e.g. [`satellite.js`](https://github.com/shashwatak/satellite-js)) and feed positions in via props, or use `useSimulationScene` to read live `Position_BN_N` from the engine.

### Astro UX Components

Mission operations components following [Astro UX Design System](https://www.astrouxds.com/) specifications.

| Component | Description |
|-----------|-------------|
| `GlobalStatusBar` | Application header with app name, clock, monitoring icons |
| `MissionClock` | UTC/local time display with multiple formats |
| `ClassificationBanner` | Security classification banner (CUI through Top Secret) |
| `Timeline` | Gantt, list, and scatter views for mission events |
| `Progress` | Determinate and indeterminate progress indicators |
| `Notification` | Toast notifications with status and auto-dismiss |

---

## Theme System

Wrap your application in `ThemeProvider` to enable the full token-based design system.

```tsx
import { ThemeProvider, useTheme } from '@zendir/ui/react';

<ThemeProvider
  defaultVariant="hybrid"    // 'astro' | 'purple-hue' | 'hybrid' | 'nebula' |
                             // 'transparent' | 'transparent-bold' | 'transparent-minimal'
  defaultMode="dark"         // 'light' | 'dark'
  persist={true}             // save to localStorage
>
  <App />
</ThemeProvider>
```

Seven theme variants are available: `hybrid` (default, Astro status colors + purple accents), `nebula` (palette-native — Ultraviolet accent on Zendir neutral surfaces), `astro` (AstroUXDS reference, dark + light), `purple-hue`, and the glassmorphic `transparent` / `transparent-bold` / `transparent-minimal` family.

Access tokens anywhere:

```tsx
const { tokens, mode, setMode } = useTheme();

<div style={{
  background: tokens.colors.background.surface,
  color: tokens.colors.text.primary,
  padding: tokens.spacing.md,
  borderRadius: tokens.borderRadius.md,
}}>
  ...
</div>
```

**Token categories:** colors (background, border, text, status, semantic, accent, interactive) · spacing · border radius · typography (Astro-compliant scale) · shadows · animation · focus

Tokens flow from primitive palettes (`src/tokens/palettes/` — Zendir brand, Astro UXDS status, Tailwind v4.2 scales) through semantic themes (`src/tokens/themes.ts`) into generated CSS variables (`@zendir/ui/tokens/css`, `--zendir-*`) and the `ThemeProvider` runtime. See `THEME_ARCHITECTURE.md` for the full layering model.

### Card Accent System

Automatically assign accent colors to cards by content domain:

```tsx
import { CardAccentProvider, useCardAccent } from '@zendir/ui/react';

<CardAccentProvider accentMode="mix">
  <Container title="Power Systems" accentColor={getAccentColor('Power Systems')}>
    ...
  </Container>
</CardAccentProvider>
```

**Accent modes:** `cyan` · `electric` · `purple` · `teal` · `prussianBlue` · `green` · `amber` · `mix`

---

## SDK Integration (Optional)

For live API integration, add the Zendir TypeScript SDK (`zendir-ts`):

```bash
npm install zendir-ts
```

`useZendirSession` provisions the container + simulation lifecycle.
`useSimulationScene` is the canonical bridge — it polls `Position_BN_N`,
buckets the simulation structure, and feeds props straight into `<ZenSpace3D />`.

```tsx
import { useZendirSession, useSimulationScene, ZenSpace3D } from '@zendir/ui/react';

function MissionControl() {
  // Lifecycle: createContainer → waitForRunning → createSimulation
  const { client, session, connect, disconnect, isLoading, error } =
    useZendirSession({ apiKey: 'your-key', autoConnect: true });

  // Live data: structure + position polling, ECI→ECEF on the Cesium backend
  const scene = useSimulationScene({
    client,
    containerId: session?.containerId ?? null,
    simulationId: session?.simulationId ?? null,
  });

  if (error)         return <div>Error: {error}</div>;
  if (isLoading)     return <div>Connecting…</div>;
  if (!session)      return <button onClick={connect}>Connect</button>;

  return (
    <ZenSpace3D
      satellites={scene.satellites}
      groundStations={scene.groundStations}
      referenceTime={scene.referenceDate}
    />
  );
}
```

| Hook | Purpose |
|---|---|
| `useZendirSession` | Container + simulation lifecycle (`createContainer` → `waitForRunning` → `createSimulation`); `disconnect` tears down |
| `useSimulationScene` | Bridges a `ZendirClient` to live `<ZenSpace3D />` props (satellites, ground stations, custom objects, reference time) |
| `useAccessWindows` | Pure client-side AOS/LOS pass-timing math from a list of `AccessPass` records (no SDK call) |

---

## AI Integration

The `ChatPanel` component provides a complete AI-powered operator interface with structured responses, interactive blocks, and four integration strategies — including first-class MCP (Model Context Protocol) support.

### Quick Setup

```tsx
import { ChatPanel, parseChatResponse } from '@zendir/ui/react';

function OperatorChat() {
  const [messages, setMessages] = useState([]);

  const handleSend = async (text) => {
    setMessages(prev => [...prev, { id: Date.now(), role: 'user', content: text, timestamp: Date.now() }]);
    const response = await callYourAI(text);
    const msg = parseChatResponse(response);   // auto-detects JSON, YAML, or plain text
    setMessages(prev => [...prev, msg]);
  };

  return <ChatPanel messages={messages} onSend={handleSend} title="Mission AI" />;
}
```

### Four Integration Strategies

| Strategy | Token Cost | Best For |
|----------|-----------|----------|
| **Tool/Function Calling** | Lowest | OpenAI, Anthropic, Gemini (production) |
| **MCP (Model Context Protocol)** | Lowest | Multi-tool, multi-server architectures |
| **YAML Prompt** | ~30-40% less than JSON | Any LLM, cost-sensitive deployments |
| **JSON Prompt** | Baseline | Simplest setup, widest LLM compatibility |

#### Strategy 1: Tool / Function Calling _(recommended for single-provider)_

```ts
import { CHAT_RESPONSE_TOOL_SCHEMA, parseChatResponse } from '@zendir/ui/react';

// OpenAI
const res = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: history,
  tools: [{ type: 'function', function: CHAT_RESPONSE_TOOL_SCHEMA }],
  tool_choice: { type: 'function', function: { name: 'respond_to_operator' } },
});
const msg = parseChatResponse(res.choices[0].message.tool_calls[0].function.arguments, 'json');

// Anthropic
tools: [{ name: CHAT_RESPONSE_TOOL_SCHEMA.name,
          description: CHAT_RESPONSE_TOOL_SCHEMA.description,
          input_schema: CHAT_RESPONSE_TOOL_SCHEMA.parameters }]

// Google Gemini
tools: [{ functionDeclarations: [CHAT_RESPONSE_TOOL_SCHEMA] }]
```

#### Strategy 2: MCP (Model Context Protocol) _(recommended for multi-tool)_

```ts
// Server: Register your tool with the pre-built schema
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { CHAT_RESPONSE_MCP_TOOL } from '@zendir/ui';

const server = new McpServer({ name: 'ops-assistant', version: '1.0.0' });
server.tool(
  CHAT_RESPONSE_MCP_TOOL.name,
  CHAT_RESPONSE_MCP_TOOL.description,
  CHAT_RESPONSE_MCP_TOOL.inputSchema,
  async (args) => ({ content: [{ type: 'text', text: JSON.stringify(args) }] })
);

// Client: Convert MCP results to ChatPanel messages
import { parseMcpToolResult } from '@zendir/ui/react';

const result = await mcpClient.callTool({ name: 'check_health', arguments: { id: 'SAT-001' } });
const msg = parseMcpToolResult('check_health', result);
```

#### Strategy 3: YAML Prompt

```ts
import { CHAT_RESPONSE_YAML_PROMPT, CHAT_STATUS_RULES_PROMPT, parseChatResponse } from '@zendir/ui/react';

const systemPrompt = `You are a spacecraft ops assistant.\n\n${CHAT_RESPONSE_YAML_PROMPT}\n\n${CHAT_STATUS_RULES_PROMPT}`;
const msg = parseChatResponse(aiOutput, 'yaml');
```

#### Strategy 4: JSON Prompt

```ts
import { CHAT_RESPONSE_JSON_PROMPT, CHAT_STATUS_RULES_PROMPT, parseChatResponse } from '@zendir/ui/react';

const systemPrompt = `You are a spacecraft ops assistant.\n\n${CHAT_RESPONSE_JSON_PROMPT}\n\n${CHAT_STATUS_RULES_PROMPT}`;
const msg = parseChatResponse(aiOutput, 'json');
```

### Global Configuration

```ts
import yaml from 'js-yaml';
import { createChatResponseParser } from '@zendir/ui/react';

// Create once, use everywhere
export const parseAI = createChatResponseParser({
  yamlParser: (s) => yaml.load(s),    // plug in production YAML parser
  defaultFormat: 'yaml',               // default format
});

const msg = parseAI(aiOutput);
```

### Structured Blocks

AI responses can include rich structured content:

| Block Type | Description | Interactive |
|-----------|-------------|-------------|
| `alert` | Status banner with severity shape | No |
| `telemetry` | Key-value readout with per-row status | No |
| `progress` | Progress bar with status color | No |
| `table` | Data table with column headers | No |
| `kv` | Compact key-value metadata | No |
| `command` | Code block with copy/run buttons | Yes |
| `choice` | Single or multi-select options | Yes |
| `confirm` | Confirmation gate (authorize/abort) | Yes |

Interactive blocks fire `onBlockEvent` with `{ blockId, messageId, action, value }`.

### Exported Helpers

| Export | What It Does |
|--------|-------------|
| `CHAT_RESPONSE_TOOL_SCHEMA` | Function/tool schema for OpenAI, Anthropic, Gemini |
| `CHAT_RESPONSE_MCP_TOOL` | MCP server tool definition (for `server.tool()` registration) |
| `parseChatResponse()` | Universal parser: auto-detects JSON / YAML / plain text |
| `parseMcpToolResult()` | Bridge MCP tool results → ChatMessage with blocks |
| `createChatResponseParser()` | Factory for pre-configured parser (custom YAML, format, IDs) |
| `CHAT_RESPONSE_JSON_PROMPT` | JSON format instructions + block schema |
| `CHAT_RESPONSE_YAML_PROMPT` | YAML format instructions + block schema |
| `CHAT_STATUS_RULES_PROMPT` | Astro UX status thresholds (battery, temp, signal, memory) |
| `McpToolResult` / `McpToolContent` | Lightweight MCP types (no SDK dependency needed) |

> See the **[AI Integration Guide](https://storybook.zendir.io/?path=/story/core-chatpanel--ai-integration-guide)** and **[MCP Integration](https://storybook.zendir.io/?path=/story/core-chatpanel--mcp-integration)** stories in Storybook for interactive demos.

---

## AI Host / MCP Integration

Components are designed to embed in any AI host — ChatGPT Apps, Anthropic MCP Apps, Google Gemini, or any MCP-compatible environment. The SDK includes dedicated hooks and a universal widget wrapper:

### AppCard — Universal Widget Wrapper

`AppCard` is the base widget wrapper for any AI host — ChatGPT Apps, Anthropic MCP Apps, Google Gemini, or any MCP-compatible environment:

```tsx
import { AppCard, useToolOutput, useCallTool } from '@zendir/ui/react';

function SatelliteWidget() {
  const data = useToolOutput<SatelliteHealth>();
  const { callTool, isLoading, error } = useCallTool();

  return (
    <AppCard
      title="SAT-001"
      subtitle="Health Monitor · LEO 421km"
      status={{ level: 'caution', label: 'Battery Low' }}  // Astro UX dual-coded (shape + color)
      loading={isLoading}
      error={error?.message}
      onRetry={() => callTool('refresh_telemetry', { id: 'SAT-001' })}
      allowFullscreen
      icon={<span>🛰️</span>}
    >
      <TelemetryDisplay data={data} />
    </AppCard>
  );
}
```

**Enterprise features:** Error boundary (children never crash host), loading skeleton, error display with retry, Astro UX 6-level status badges with shapes, WCAG 2.1 AA ARIA labels, `maxHeight` auto-constraint, theme sync, fullscreen support.

### MCP Server → ChatPanel Bridge

Register your MCP server tools to produce ChatPanel-compatible structured blocks:

```ts
// server.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { CHAT_RESPONSE_MCP_TOOL } from '@zendir/ui';

const server = new McpServer({ name: 'sat-ops', version: '1.0.0' });
server.tool(
  CHAT_RESPONSE_MCP_TOOL.name,
  CHAT_RESPONSE_MCP_TOOL.description,
  CHAT_RESPONSE_MCP_TOOL.inputSchema,
  async (args) => ({ content: [{ type: 'text', text: JSON.stringify(args) }] })
);
```

```tsx
// client.tsx — Parse MCP results into ChatPanel messages
import { parseMcpToolResult, ChatPanel } from '@zendir/ui/react';

const result = await mcpClient.callTool({ name: 'check_health', arguments: { id: 'SAT-001' } });
const msg = parseMcpToolResult('check_health', result);  // → ChatMessage with typed blocks
```

### Available Host Hooks

| Hook | Purpose |
|------|---------|
| `useToolOutput()` | Read tool result (structuredContent) |
| `useToolInput()` | Read tool invocation arguments |
| `useCallTool()` | Call other MCP tools from widget |
| `useSendMessage()` | Insert follow-up user messages |
| `useChatGPTTheme()` | Match light/dark theme |
| `useWidgetState()` | Persist widget state across renders |
| `useMaxHeight()` | Get host height constraint |
| `useDisplayMode()` | Manage inline/PiP/fullscreen |
| `isInChatGPT()` | Detect ChatGPT environment |

---

## Project Structure

```
src/
├── react/
│   ├── core/            # Buttons, inputs, dialogs, data table
│   ├── astro/           # Astro UX — timeline, status bar, clock
│   ├── cards/           # Spacecraft, telemetry, orbit, access
│   ├── charts/          # 40+ ECharts components
│   ├── visualizations/  # Link budget, thermal, propulsion
│   ├── 3d/              # Earth viewer, solar system
│   ├── hooks/           # Data hooks
│   ├── theme/           # ThemeProvider runtime, theme types, foundation stories
│   ├── chatgpt/         # AI host / MCP integration (AppCard, hooks)
│   └── shared/          # Error boundaries, skeletons, utils
└── tokens/              # Design tokens
    ├── palettes/        # Primitive color scales (Zendir, Astro UXDS, Tailwind v4.2)
    ├── themes.ts        # Semantic theme definitions (7 variants)
    ├── core.ts          # Shared non-color tokens (spacing, typography, shadows)
    └── tokens.css       # Generated CSS variables (+ css-vars.ts) — npm run build:tokens
```

## Development

```bash
git clone https://github.com/zendir-dev/zendir-ui.git
cd zendir-ui && npm install

npm run storybook     # Component playground
npm run demo          # Full demo app
npm run build         # Production build
npm run test          # Run tests
npm run lint          # Lint check
```

## Accessibility

Zendir UI conforms to [**WCAG 2.2 Level AA**](https://www.w3.org/TR/WCAG22/) and is designed to satisfy the [**European Accessibility Act (EAA)**](https://ec.europa.eu/social/main.jsp?catId=1202) via [**EN 301 549 v3.2.1**](https://www.etsi.org/deliver/etsi_en/301500_301599/301549/03.02.01_60/en_301549v030201p.pdf) and the **Revised Section 508**.

- **Keyboard** — every component is fully operable via keyboard. Focus order follows DOM order, focus indicators meet 3:1 contrast, and no keyboard traps exist.
- **Screen readers** — semantic HTML, ARIA roles, live regions for dynamic content, and descriptive labels on all icon-only controls.
- **Color** — status indicators use dual coding (color + shape). All text meets 4.5:1 contrast; accent colors are dynamically adjusted at runtime to guarantee compliance across all themes.
- **Motion** — all animations and transitions respect `prefers-reduced-motion` globally via CSS and JavaScript.
- **Target size** — interactive controls meet the 24 × 24 px minimum (WCAG 2.5.8).
- **Forms** — validation errors are identified with `aria-invalid`, associated via `aria-describedby`, and announced with `role="alert"`.

| Standard | Status |
|----------|--------|
| WCAG 2.2 Level AA | Conformant |
| EN 301 549 v3.2.1 Clause 11 | Designed for conformance |
| EAA Directive (EU) 2019/882 | Designed for conformance |
| Revised Section 508 | Conformant |

Testing: [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) in CI, [axe-core](https://storybook.js.org/addons/@storybook/addon-a11y) on all Storybook stories, manual keyboard and screen reader verification (VoiceOver, NVDA, TalkBack).

Accessibility issues are treated as **P0 bugs**. [Report an issue](https://github.com/zendir-dev/zendir-ui/issues) with the `accessibility` label.

---

## Browser Support

Chrome 90+ · Firefox 88+ · Safari 14+ · Edge 90+

WebGL required for 3D components (graceful fallback to 2D).


## License

[MIT](LICENSE) — Space Services Australia Pty Ltd

---

<p align="center">
  <a href="https://www.astrouxds.com/">Astro UX Design System</a> · <a href="https://storybook.zendir.io">Storybook</a> · <a href="https://github.com/zendir-dev/zendir-ui/issues">Issues</a> · <a href="CONTRIBUTING.md">Contributing</a>
</p>
