Web Components
Seamless integration with Web Components streamlines the testing and development of features across production and various environments. The Basestack Feature Flags Web Components (@basestack/flags-wc) provides a framework-agnostic way to integrate feature preview and feedback modals into any web application, whether you're using React, Vue, Angular, Svelte, or plain HTML.
This package exposes custom elements that work natively in the browser, eliminating the need for a JavaScript SDK while providing ready-to-use UI components for browsing preview flags and collecting user feedback.
Using React? Check out the React SDK for built-in Web Components integration via FeatureFlagModalsProvider and useFeatureFlagModals().
What this package provides
feature-flag-preview-modal: Lets users browse and toggle preview flags.feature-flag-feedback-modal: Collects user feedback for a specific feature flag.registerFeatureFlagComponents(): Registers both custom elements once in the browser.FeatureFlagClientand helper functions: Optional headless SDK for fetch + local storage state.
Getting started
Install
Install the package using your preferred package manager. This package is ESM-only your app or runtime must support ES modules.
npm install @basestack/flags-wcpnpm install @basestack/flags-wcyarn add @basestack/flags-wcbun add @basestack/flags-wcRegister components
Call registration early in your app bootstrap (browser only). This defines the custom elements feature-flag-preview-modal and feature-flag-feedback-modal.
import { registerFeatureFlagComponents } from "@basestack/flags-wc";
await registerFeatureFlagComponents();Add component tags
Add the custom elements to your markup. Use project-key to authenticate with your Basestack project.
<feature-flag-preview-modal
open
project-key="YOUR_PROJECT_KEY"
heading="Feature preview"
></feature-flag-preview-modal>
<feature-flag-feedback-modal
flag-key="command-palette"
project-key="YOUR_PROJECT_KEY"
heading="Feedback"
></feature-flag-feedback-modal>You can find your project key in your Basestack Feature Flags Dashboard.
Make sure to add your .env file to .gitignore to keep your keys secure. Never commit sensitive credentials to version control.
Framework integration
The Web Components work in any framework. Below are examples for common setups.
Plain HTML / Vanilla JS
<!doctype html>
<html lang="en">
<body>
<button id="open-preview">Open preview modal</button>
<feature-flag-preview-modal id="preview-modal" project-key="YOUR_PROJECT_KEY">
</feature-flag-preview-modal>
<script type="module">
import { registerFeatureFlagComponents } from "@basestack/flags-wc";
await registerFeatureFlagComponents();
const preview = document.getElementById("preview-modal");
document.getElementById("open-preview").addEventListener("click", () => {
preview.open = true;
});
</script>
</body>
</html>React
import { useEffect, useState } from "react";
import { registerFeatureFlagComponents } from "@basestack/flags-wc";
export function FeatureFlagsEntry() {
const [ready, setReady] = useState(false);
useEffect(() => {
registerFeatureFlagComponents().then(() => setReady(true));
}, []);
return (
<>
<button
onClick={() => {
const el = document.querySelector("feature-flag-preview-modal") as any;
if (el) el.open = true;
}}
disabled={!ready}
>
Open preview
</button>
<feature-flag-preview-modal project-key="YOUR_PROJECT_KEY" heading="Feature preview" />
</>
);
}Vue
import { createApp } from "vue";
import App from "./App.vue";
import { registerFeatureFlagComponents } from "@basestack/flags-wc";
await registerFeatureFlagComponents();
createApp(App).mount("#app");<template>
<button @click="openModal">Open preview</button>
<feature-flag-preview-modal ref="modal" project-key="YOUR_PROJECT_KEY" />
</template>
<script setup lang="ts">
import { ref } from "vue";
const modal = ref<any>(null);
function openModal() {
if (modal.value) modal.value.open = true;
}
</script>Angular
In main.ts (or app bootstrap), register components first:
import { registerFeatureFlagComponents } from "@basestack/flags-wc";
await registerFeatureFlagComponents();Then allow custom elements in your module using CUSTOM_ELEMENTS_SCHEMA.
Component reference
feature-flag-preview-modal
Shows a two-pane modal where users can:
- Load available preview flags from an API
- Select a flag
- Toggle enabled state
- Persist selection in
localStorage
Attributes / properties
Use kebab-case in HTML (e.g. project-key, api-endpoint) and camelCase when setting as JS properties.
| Attribute | Type | Default | Description |
|---|---|---|---|
theme | "light" | "dark" | "light" | Visual theme. |
api-endpoint | string | https://flags.basestack.co/api/v1/flags/preview | API endpoint used to fetch flags. |
project-key | string | - | Sends x-project-key header. |
environment-key | string | - | Sends x-environment-key header. |
storage-key | string | bs-flags-preview-state | localStorage key for toggle state. |
open | boolean | false | Controls modal visibility. |
heading | string | "Feature preview" | Dialog title. |
subtitle | string | "Get early access to new features and let us know what you think." | Supporting text below title. |
close-label | string | "Close" | Accessible label for close controls. |
show-close-button | boolean | true | Shows/hides top-right close button. |
loading-label | string | "Loading features..." | Loading state text. |
empty-label | string | "No feature flags available at the moment." | Empty state text. |
selection-prompt | string | "Choose a feature to view" | Prompt when nothing selected. |
selection-placeholder | string | "Select a feature on the left to see its details." | Details pane placeholder text. |
enable-label | string | "Enable" | Button text when selected flag is disabled. |
enabled-label | string | "Disable" | Button text when selected flag is enabled. |
preview-badge-label | string | "Preview" | Badge text for non-expiring flag. |
expires-soon-label | string | "Expires soon" | Badge text for expiring flag. |
learn-more-label | string | "Learn more" | Link text for docs URL. |
Events
flagChange: Emitted when selected flag is toggled.event.detail:{ slug: string; enabled: boolean; flag?: FeatureFlag }
closed: Emitted when user closes the modal.
Example
<feature-flag-preview-modal
open
api-endpoint="https://flags.basestack.co/api/v1/flags/preview"
project-key="YOUR_PROJECT_KEY"
storage-key="bs-flags-preview-state"
heading="Feature preview"
subtitle="Select and enable previews for your workflow."
selection-prompt="Select a preview to inspect"
selection-placeholder="Choose a feature on the left to preview details."
enable-label="Enable"
enabled-label="Disable"
loading-label="Loading feature previews..."
empty-label="No feature previews are currently available."
preview-badge-label="Preview"
expires-soon-label="Expiring soon"
learn-more-label="Learn more"
></feature-flag-preview-modal>Listening to events
const preview = document.querySelector("feature-flag-preview-modal");
preview?.addEventListener("flagChange", (event: any) => {
console.log("Flag changed:", event.detail.slug, event.detail.enabled);
});
preview?.addEventListener("closed", () => {
console.log("Preview modal closed");
});feature-flag-feedback-modal
Shows a feedback form for a specific flag (flag-key) and sends payload to the feedback endpoint.
Required input
flag-keyis required.- At least one of
project-keyorenvironment-keyshould be provided for authenticated requests.
Attributes / properties
| Attribute | Type | Default | Description |
|---|---|---|---|
theme | "light" | "dark" | "light" | Visual theme. |
flag-key | string | - | Basestack flag key this feedback belongs to. |
feature-name | string | - | Human-friendly feature label shown in header. |
project-key | string | - | Sends x-project-key header. |
environment-key | string | - | Sends x-environment-key header. |
api-endpoint | string | https://flags-api.basestack.co/v1/flags/feedback | POST endpoint for feedback. |
open | boolean | false | Controls modal visibility. |
heading | string | "Feedback" | Dialog title. |
mood-prompt | string | "How are you feeling after using this feature?" | Label for mood selector. |
rating-prompt | string | "How satisfied are you with this feature?" | Label for rating selector. |
feedback-label | string | "Share your feedback" | Textarea label. |
feedback-placeholder | string | "Anything else you'd like to add?" | Textarea placeholder. |
submit-label | string | "Submit feedback" | Submit button text. |
privacy-policy-url | string | https://basestack.co/legal/privacy | Privacy policy link target. |
privacy-policy-label | string | "To keep your data safe, avoid sharing personal or sensitive details." | Privacy helper copy. |
privacy-policy-link-label | string | "Learn more" | Privacy link text. |
close-label | string | "Close" | Accessible label for close controls. |
metadata | Record<string, unknown> | - | Optional JSON-serializable data sent with feedback (e.g. user, payment, context). Set via JS property. |
Events
feedbackSent: Emitted after successful POST.event.detail:{ flagKey, mood, rating, description }
closed: Emitted when modal is closed.
Example
<feature-flag-feedback-modal
open
flag-key="command-palette"
feature-name="Command palette"
project-key="YOUR_PROJECT_KEY"
heading="Feedback"
mood-prompt="How did this feature make you feel?"
rating-prompt="How would you rate this feature?"
feedback-label="Your feedback"
feedback-placeholder="Anything else you would like to share?"
submit-label="Send feedback"
></feature-flag-feedback-modal>Sending custom metadata
Use the optional metadata prop to attach any JSON-serializable data to feedback submissions. This is useful for enriching feedback with user info, payment context, or custom tracking data. Set it via the JavaScript property (not as an HTML attribute):
// React / JSX
<feature-flag-feedback-modal
flag-key="command-palette"
project-key="YOUR_PROJECT_KEY"
metadata={{
user: { id: "usr_demo_002", email: "[email protected]" },
payment: { customerId: "cus_demo_002", plan: "lite", trial: true },
context: { source: "vite-react-example" },
}}
/>// Vanilla JS
const feedbackEl = document.querySelector("feature-flag-feedback-modal");
if (feedbackEl) {
(feedbackEl as any).metadata = {
user: { id: "usr_demo_002", email: "[email protected]" },
payment: { customerId: "cus_demo_002", plan: "lite", trial: true },
context: { source: "vanilla-html" },
};
}API and data behavior
Preview modal fetch behavior
- If
api-endpointis default and noproject-key/environment-keyis provided, it shows built-in fallback mock flags. - Otherwise it fetches JSON from the endpoint and expects:
- either an array of flags, or
- an object with
flagsarray.
- Each flag should include at least:
slug,description.
Optional fields used by UI: name, previewName, previewImage, previewContent, documentationLink, expiredAt.
Feedback modal submit behavior
Sends:
- Method:
POST - URL:
api-endpoint(defaults tohttps://flags-api.basestack.co/v1/flags/feedback) - Headers:
Content-Type: application/jsonx-project-keyifproject-keyexistsx-environment-keyifenvironment-keyexists
- Body:
{
"flagKey": "command-palette",
"mood": "HAPPY",
"rating": 4,
"description": "Great experience",
"metadata": {
"user": { "id": "usr_demo_002", "email": "[email protected]" },
"payment": { "customerId": "cus_demo_002", "plan": "lite", "trial": true },
"context": { "source": "vite-react-example" }
}
}When metadata is provided, it is merged into the request body as shown above.
Headless SDK usage (optional)
If you want to control logic outside of UI components:
import {
FeatureFlagClient,
fetchFeatureFlags,
readSelectionState,
writeSelectionState,
toggleFlagState,
} from "@basestack/flags-wc";FeatureFlagClient
const client = new FeatureFlagClient(
"https://flags.basestack.co/api/v1/flags/preview",
"bs-flags-preview-state",
);
const flags = await client.listFlags({
projectKey: "YOUR_PROJECT_KEY",
});
const state = client.loadSelections();
client.setSelection("command-palette", true);
client.writeSelections({ ...state, dashboard: true });Working with open/close state
Both modals use an open boolean property.
- Open from JS:
element.open = true - Close from JS:
element.open = false - User close interactions: overlay click,
Escapekey, close button
TypeScript and JSX support
The package exports JSX intrinsic typings so component tags and props are typed in TS/TSX environments. In TSX, for the feedback modal, flagKey / flag-key is required.
import type { RefAttributes } from "react";
export interface FeatureFlagPreviewModalElement extends HTMLElement {
open: boolean;
}
export interface FeatureFlagFeedbackModalElement extends HTMLElement {
open: boolean;
flagKey: string;
featureName?: string;
metadata?: Record<string, unknown>;
}
declare module "react" {
namespace JSX {
interface IntrinsicElements {
"feature-flag-preview-modal": RefAttributes<FeatureFlagPreviewModalElement> & {
"api-endpoint": string;
"project-key": string;
"environment-key": string;
} & Partial<{
theme: string;
heading: string;
subtitle: string;
"selection-prompt": string;
"selection-placeholder": string;
"enable-label": string;
"enabled-label": string;
"loading-label": string;
"empty-label": string;
"preview-badge-label": string;
"expires-soon-label": string;
"learn-more-label": string;
}>;
"feature-flag-feedback-modal": RefAttributes<FeatureFlagFeedbackModalElement> & {
"api-endpoint": string;
"project-key": string;
"environment-key": string;
} & Partial<{
theme: string;
"flag-key": string;
"feature-name": string;
metadata: Record<string, unknown>;
heading: string;
"mood-prompt": string;
"rating-prompt": string;
"feedback-label": string;
"feedback-placeholder": string;
"submit-label": string;
"privacy-policy-url": string;
"privacy-policy-label": string;
"privacy-policy-link-label": string;
}>;
}
}
}SSR and server rendering
Register components only in the browser. If your framework renders on the server, guard initialization with browser checks (typeof window !== "undefined"). Local storage helpers are no-op on non-browser environments.
Best practices
1. Register once at app startup
Call registerFeatureFlagComponents() once during your app bootstrap. Do not call it repeatedly or inside component render cycles.
2. Provide authentication headers
Always provide project-key and/or environment-key for production to fetch real flag data and submit feedback.
3. Customize copy for your product
Use the label attributes (heading, subtitle, enable-label, etc.) to match your product's tone and locale.
4. Handle emitted events
Attach listeners for flagChange, feedbackSent, and closed for analytics, telemetry, or UI updates.
5. Verify CORS configuration
Ensure your API endpoint allows your frontend origin for cross-origin requests.
Troubleshooting
"Custom element is not defined"
- Ensure
registerFeatureFlagComponents()runs before rendering/using tags. - Ensure import comes from
@basestack/flags-wc. - Ensure your bundler loads ESM packages.
Preview modal shows mock flags
- This is expected when using the default endpoint without project/environment headers.
- Add
project-keyorenvironment-keyto fetch real data.
Feedback submit fails
- Ensure
flag-keyis present. - Provide at least one of
project-keyorenvironment-key. - Check network/CORS and endpoint URL.
Event handlers not firing
- Attach listeners after the element exists in the DOM.
- Listen with exact event names:
flagChange,feedbackSent,closed.
Production checklist
- Register components once at app startup
- Provide auth header key(s):
project-keyand/orenvironment-key - Customize copy labels for your product/locale
- Handle emitted events for analytics/telemetry
- Verify endpoint CORS allows your frontend origin
Next steps
- Explore the React SDK for built-in Web Components integration with
FeatureFlagModalsProvideranduseFeatureFlagModals(). - Learn about flag management in the Basestack dashboard.