@strata-ds/web
v1.0.1
Published
Strata Design System Web Components
Downloads
275
Readme
@strata-ds/web
🌐 Framework-agnostic web components for Strata — connect any frontend to your design system with zero lock-in.
@strata-ds/web is a Stencil-powered component library that delivers Strata design tokens and components to vanilla JS, React, Vue, Angular, Svelte, and any framework that supports web standards. It ensures design-to-code fidelity across your entire frontend ecosystem.
Design once in Strata. Ship everywhere.
Specs
Durable, versioned contracts for agent-driven rendering and related runtime
interfaces live in docs/specs/.
AI coding agents must read AGENTS.md before contributing.
Table of Contents
- Specs
- Why @strata-ds/web?
- Who Is This For?
- Core Features
- 5-Minute Integration Checklist
- Quick Start
- API Reference
- Debugging & Troubleshooting
- Architecture Overview
- Authentication
- Advanced Usage
- License
- Support
Why @strata-ds/web?
The Problem
Modern teams use multiple frameworks (React for marketing site, Vue for admin panel, vanilla JS for docs). Without a framework-agnostic design system, you face:
- Duplicate style implementations across frameworks
- Inconsistent brand experience (React button ≠ Vue button)
- Manual token updates in 5+ repositories
- Designer → developer handoff breaks down per framework
Key Pain Points:
- ⚠️ Stale Tokens: Design tokens become outdated across multiple codebases.
- ❌ No Source of Truth: No single source of truth for component styling.
- 🔄 Manual Sync: Manual synchronization between design and code is slow and error-prone.
- 📉 Platform Divergence: Platform-specific implementations diverge over time.
The Solution
@strata-ds/web uses Web Components (browser-native, framework-agnostic) to deliver:
- ✅ One source of truth — Tokens and components sync from Strata
- ✅ Works everywhere — Vanilla JS, React, Vue, Angular, Svelte, etc.
- ✅ Real-time updates — Design changes propagate without redeploying
- ✅ Zero lock-in — Web standards, not proprietary APIs
- ✅ CSS variable injection for global styling
- ✅ Dynamic component rendering based on API-defined structure
Who Is This For?
| Persona | Value | |---------|-------| | Design System Teams | Single source of truth for variables and components | | Solopreneurs | "I'm using vanilla JS — I don't need React bloat. I just want brand-consistent components." | | Frontend Engineers | "I maintain 3 apps in different frameworks. One design system = one source of truth." | | Multi-Framework Teams | Consistent UI across React, Vue, Angular, and static sites | | Product Teams | Real-time style updates without redeployment | | Entrepreneurs | "My team ships faster because components stay in sync across React, Vue, and marketing pages." | | Designers | "What I create in Strata works everywhere — no framework-specific tweaks." |
Core Features
- 🌐 Framework-agnostic — Works in vanilla JS, React, Vue, Angular, Svelte
- 🔁 Live sync with Strata backend
- 🧠 Token-driven styling — Design tokens as CSS custom properties
- 🧩 Dynamic component loading — Fetch components from Strata on-demand
- 🛠 Framework wrappers — First-class React and Vue support
- 📴 Offline-first fallback — Cached data when network fails
- 🧪 Debug tools —
<strata-debug>component for inspection
Before you start:
- [ ] Created a project at strata.charisol.io
- [ ] Imported your design tokens (Style Dictionary JSON or Figma)
- [ ] Published your project (dashboard shows "✅ Published")
Integration:
- [ ] Installed
@strata-ds/webvia npm/yarn - [ ] Copied
projectIdfrom dashboard URL or project settings - [ ] Copied
projectTokenfrom Publish & Sync tab (starts withpt_live_) - [ ] Added
<strata-provider>wrapper - [ ] Loaded the Stencil script in your HTML/framework entry point
- [ ] Rendered your first component with
<strata-dynamic>
Verification:
- [ ] Opened DevTools → Console and saw
Connected to design system - [ ] Console shows
Design system synced: { components: {...}, variables: {...} } - [ ] Component renders on screen with correct styles
- [ ] Added
<strata-debug></strata-debug>and verified connection status
Stuck? → Jump to Debugging & Troubleshooting.
Quick Start (< 5 Minutes)
🚀 New to Strata? Get your first component rendering in 3 steps.
Step 1: Install
npm install @strata-ds/web
# or
yarn add @strata-ds/webStep 2: Get Your Credentials
Go to strata.charisol.io → Your Project → Publish & Sync tab:
Copy your Project ID (e.g., abc123def456)
Copy your Project Token (starts with pt_live_)
Step 3: Load the Components
Vanilla JS / HTML
<!DOCTYPE html>
<html>
<head>
<script type="module" src="https://cdn.jsdelivr.net/npm/@strata-ds/web/dist/strata-web/strata-web.esm.js"></script>
</head>
<body>
<strata-provider
url="http://localhost:4000"
enable-realtime="true"
poll-interval="6000"
project-id="YOUR_PROJECT_ID"
project-token="pt_live_your_token_here"
sync-enabled="true"
>
<!-- Dynamic component -->
<strata-dynamic
component="button"
props='{"children": "Click me!", "variant": "primary"}'
></strata-dynamic>
</strata-provider>
</body>
</html>💡 Replace
YOUR_PROJECT_IDandpt_live_your_token_herewith values from Step 2.
React (Next.js / Create React App)
Step 1: Create a Provider Component
👉 app/providers/StrataProvider.tsx
<<<<<<< HEAD
// app/layout.tsx (Next.js App Router)
"use client";
import { StrataProvider } from "@strata-ds/web/react";
=======
'use client';
import { useEffect, useRef } from 'react';
import { defineCustomElements } from '@strata-ds/web/loader';
export default function StrataProvider({
children,
}: {
children: React.ReactNode;
}) {
const providerRef = useRef<any>(null);
useEffect(() => {
const load = async () => {
try {
await defineCustomElements();
console.log('✅ Strata web components registered');
} catch (err) {
console.error('❌ Failed to register Strata:', err);
}
};
load();
}, []);
return (
<strata-provider
ref={providerRef}
url="http://localhost:4000"
enable-realtime={true}
poll-interval={6000}
project-id="YOUR_PROJECT_ID"
project-token="pt_live_your_token_here"
sync-enabled={true}
>
{children}
</strata-provider>
);
}Step 2: Update the Layout (app/layout.tsx)
import './globals.css';
import { StrataProvider } from './providers/StrataProvider';
import Script from 'next/script'; // Import the Script component
>>>>>>> 020a780f53ce5f1c32b9cbf0c05e4f98f836a432
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head>
{/* Use the Script component for the CDN */}
<Script
src="https://cdn.tailwindcss.com"
strategy="beforeInteractive"
/>
</head>
<body>
<StrataProvider>
{children}
</StrataProvider>
</body>
</html>
);
}Step 3: Usage in Pages (app/page.tsx)
👉 app/page.tsx
// app/page.tsx
<<<<<<< HEAD
"use client";
import { StrataDynamic } from "@strata-ds/web/react";
=======
'use client';
>>>>>>> 020a780f53ce5f1c32b9cbf0c05e4f98f836a432
export default function Page() {
return (
<main>
<h1>Strata Design System Test</h1>
<h2>Card Component</h2>
<strata-dynamic component="card" sync-enabled={true} />
<h2>Button Component</h2>
<strata-dynamic component="button" />
</main>
);
}⚠️ 4. Add TypeScript support for custom elements (recommended)
TypeScript: If TypeScript complains about the custom tags, ensure you have a custom-elements.d.ts file in your project:
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'strata-provider': DetailedHTMLProps<HTMLAttributes<HTMLElement> & {
url?: string;
'enable-realtime'?: boolean;
'poll-interval'?: number;
'project-id'?: string;
'sync-enabled'?: boolean;
'syncToken'?: string;
'projectToken'?: string;
}, HTMLElement>;
'strata-dynamic': DetailedHTMLProps<HTMLAttributes<HTMLElement> & {
component?: string;
props?: string;
'sync-enabled'?: boolean;
}, HTMLElement>;
'strata-debug': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
}
}
}
export { };Alternative simpler declaration:
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'strata-provider': any;
'strata-dynamic': any;
'strata-debug': any;
}
}
}
export { };Vue 3
<template>
<div>
<strata-provider
url="http://localhost:4000"
enable-realtime="true"
poll-interval="6000"
project-id="YOUR_PROJECT_ID"
project-token="pt_live_your_token_here"
sync-enabled="true"
@connected="onConnected"
@error="onError"
>
<div>
<h2>Card Component</h2>
<strata-dynamic component="card" />
<h2>Button Component</h2>
<strata-dynamic component="button"
:props="JSON.stringify({ children: 'Click me!', variant: 'primary' })"
/>
</div>
</strata-provider>
</div>
</template>
<script setup>
<<<<<<< HEAD
import { defineCustomElements } from '@strata-ds/web/loader';
=======
const onConnected = () => {
console.log('Connected to Strata')
}
>>>>>>> 020a780f53ce5f1c32b9cbf0c05e4f98f836a432
const onError = (error) => {
console.error('Strata error:', error)
}
</script>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
}
</style>Step 2 Update vite.config.ts of Vue:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
// Treat all tags with 'strata-' prefix as custom elements
isCustomElement: (tag) => tag.startsWith('strata-')
}
}
}),
vueDevTools(),
],
optimizeDeps: {
include: ['@strata-ds/web', '@strata-ds/web/loader'],
exclude: []
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})⚠️ Add TypeScript support for custom elements (recommended)
In your Vue app create a file src/strata-web.d.ts inside it add;
declare module '@strata-ds/web/vue' {
import { Plugin, Component } from 'vue'
export const StrataProvider: Component
export const StrataDynamic: Component
const StrataVuePlugin: Plugin
export default StrataVuePlugin
}Create a new file src/shims-vue.d.ts inside it add;
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}Inside the index.html of the Vue project add Tailwind CSS CDN
<!-- Add Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>Angular
// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { defineCustomElements } from '@strata-ds/web/loader';
defineCustomElements();
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}HTML
<!-- app.component.html -->
<strata-provider
url="http://localhost:4000"
enable-realtime="true"
poll-interval="6000"
project-id="YOUR_PROJECT_ID"
project-token="pt_live_your_token_here"
sync-enabled="true"
>
<strata-dynamic
component="Button"
props='{"children": "Click me!", "variant": "primary"}'
></strata-dynamic>
</strata-provider>Svelte
<!-- App.svelte -->
<script>
import { onMount } from 'svelte';
import { defineCustomElements } from '@strata-ds/web/loader';
onMount(() => {
defineCustomElements();
});
</script>
<strata-provider
url="http://localhost:4000"
enable-realtime="true"
poll-interval="6000"
project-id="YOUR_PROJECT_ID"
project-token="pt_live_your_token_here"
sync-enabled="true"
>
<strata-dynamic
component="Button"
props={JSON.stringify({ children: 'Click me!', variant: 'primary' })}
></strata-dynamic>
</strata-provider>API Reference
<strata-provider>
Wraps your app and manages the connection to Strata.
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| url | string | 'http://localhost:4000' | Base URL for the Strata backend. Use http://localhost:4000 for local development; set to your deployed Strata backend URL (use https:// in production to protect tokens) |
| enable-realtime | boolean | true | Enable WebSocket + polling for real-time updates |
| poll-interval | number | 6000 | Polling interval in milliseconds |
| project-id | string | undefined | Your Strata project ID (from dashboard) |
| project-token | string | undefined | Sync token (starts with pt_live_) — required if project is private |
| sync-enabled | boolean | true | Enable/disable syncing |
Events:
synced— Fires when design system data syncs successfullyerror— Fires when sync fails (checkevent.detail.error)connected— Fires when WebSocket connectsdisconnected- Fired when connection is lost
<strata-dynamic>
Renders a component fetched from Strata.
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| component | string | required | Name of the component (e.g., "Button", "Navbar") |
| props | string (JSON) | '{}' | Props to pass to the component (JSON string) |
| sync-enabled | boolean | true | Enable/disable syncing for this component |
Events:
load— Fires when the component loads successfullyerror— Fires when the component fails to load
Example:
<strata-dynamic
component="Card"
props='{"title": "Welcome", "variant": "elevated"}'
></strata-dynamic><strata-debug>
Renders a debug panel showing connection status and loaded components.
<strata-debug></strata-debug>Displays:
- ✅ Connection status (Connected / Disconnected / Error)
- ✅ Loaded components
- ✅ Loaded variables
- ✅ Sync mode (WebSocket + Polling)
- ✅ Last sync time
- ✅ Any errors
Debugging & Troubleshooting
Quick Debug Panel
Add <strata-debug></strata-debug> anywhere inside <strata-provider> to inspect:
- ✅ Connection status
- ✅ Loaded components
- ✅ Loaded design tokens
- ✅ Sync configuration
<strata-provider url="http://localhost:4000" project-id="abc123">
<strata-debug></strata-debug>
<!-- Your app content -->
</strata-provider>Console Log Reference
When debugging, look for these messages in DevTools Console:
- ✅
Strata Web Components loaded— Script initialized successfully - ✅
Connected to design system— Provider mounted and connected - ✅
Design system synced: { components: {...}, variables: {...} }— Data loaded - ❌
401/403— Token or CORS issue (see table above) - ❌
Component "X" doesn't exist— Component name doesn't match your Strata project
Common Issues
| 🚨 Problem | ✅ Solution |
|-----------|-----------|
| 401 Unauthorized | Your project-token is invalid or expired. Go to Publish & Sync tab in your Strata project and copy a fresh token. |
| 403 Forbidden | Your project has CORS restrictions. Add your domain to allowedOrigins in your Strata project settings. |
| Component not rendering | 1. Check project-id matches your dashboard URL2. Verify the component exists in your Strata project3. Open DevTools Console and look for errors |
| Styles broken / unstyled | Design tokens are applied as CSS custom properties on :root. Check DevTools → Elements → Styles for --color-*, --font-*, etc. |
| TypeScript errors (React) | Install type definitions: npm install --save-dev @types/react |
| Vue warning: unknown element | Call defineCustomElements() from @strata-ds/web/loader in your app entry file (see Vue example above) |
Network Tab Debugging
Open DevTools → Network tab and filter by:
- Polling requests — Look for requests to your
url(e.g.,http://localhost:4000/sync); status should be200 OKwith JSON responses - WebSocket connection — Look for
101 Switching Protocols
🏗 Architecture Overview
Your App
└── <strata-provider> ← Manages connection & token sync
├── <strata-dynamic> ← Fetches & renders individual components
└── <strata-debug> ← (Optional) Debug panel@strata-ds/web Core
The following diagram illustrates how @strata-ds/web acts as the bridge between the Design System API and the consumer frameworks.
graph TD
subgraph API ["Design System API"]
A["Variables"]
B["Components"]
C["Real-time WebSocket"]
end
API --> Core
subgraph Core ["@strata-ds/web Core"]
direction TB
subgraph Sync ["StrataSystemSyncService - Singleton"]
S1["- Fetches variables & components from API"]
S2["- Manages polling/WebSocket connections"]
S3["- Injects CSS variables into :root"]
S4["- Event emission for state changes"]
end
subgraph Factory ["Component Factory & Style Manager"]
F1["- Renders components from API definitions"]
F2["- Processes Tailwind classes and CSS variables"]
F3["- Generates inline styles from properties"]
end
end
Core --> WC["Web Component Direct Usage"]
Core --> RW["React Wrapper"]
Core --> VW["Vue Wrapper"]
%% Styling
style API fill:#f9f9f9,stroke:#333,stroke-width:2px
style Core fill:#282829,stroke:#01579b,stroke-width:2px
style Sync fill:#333,stroke:#01579b
style Factory fill:#333,stroke:#01579bAuthentication
If your project is private, pass a project-token:
<strata-provider
project-id="YOUR_PROJECT_ID"
project-token="pt_live_your_token_here"
>Every sync request will include Authorization: Bearer pt_live_... and X-Project-Token headers.
- REST API (Initial Sync)
For standard HTTP requests (fetching variables and component definitions), credentials are sent via Headers:
| Header | Value |
| :--- | :--- |
| Authorization | Bearer pt_live_xxxxxxxxxxxx |
| X-Project-Token | your-project-id |
- WebSocket (Real-time Updates)
For live synchronization, credentials must be passed as Query Parameters to ensure compatibility across all browser environments:
wss://api.strata.run/sync?token=pt_live_xxx&projectId=your-project-id💡 Why the distinction?
Headersare used for API calls to follow REST best practices and keep tokens out of server access logs.Query Parametersare used for WebSockets because the initial browser handshake for WSS does not always support custom headers reliably across all platforms (Web, Flutter, etc.).
See how the StrataSystemSyncService handles this internally:
// Internal Auth Logic
const headers = {
'Authorization': `Bearer ${token}`,
'X-Project-Token': projectId
};
const wsUrl = `${BASE_WS_URL}?token=${token}&projectId=${projectId}`;Advanced Usage
Listening to Events (Vanilla JS)
const provider = document.querySelector('strata-provider');
provider.addEventListener('synced', (event) => {
console.log('Design system loaded:', event.detail);
});
provider.addEventListener('error', (event) => {
console.error('Sync failed:', event.detail.error);
});⚛️ React Usage
import { StrataProvider } from '@strata-ds/web/react';
<StrataProvider
projectId="YOUR_PROJECT_ID"
projectToken="pt_live_your_token_here"
onSync={(data) => console.log('Synced:', data)}
onError={(error) => console.error('Error:', error)}
>
{children}
</StrataProvider>Dynamic Props
Update props dynamically:
const dynamic = document.querySelector('strata-dynamic');
dynamic.setAttribute('props', JSON.stringify({ variant: 'secondary', children: 'Updated!' }));⚠️ Note:
propsmust be a JSON string when using the web component directly. For React, use thestrata-dynamicwrapper which accepts objects directly.
License
MIT © Charisol
Resources
- 🌐 Strata Dashboard
- 📦 npm Package
- 📚 Stencil Documentation
- 💬 Community Discord (placeholder — update with real link)
