Basestack Docs
Frontend

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.

We only support ESM (ECMAScript Modules) in the browser environment. If you are using CommonJS, you will need to use a bundler like Webpack or Rollup to bundle your code.

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.
  • FeatureFlagClient and 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.

Install the SDK
npm install @basestack/flags-wc
pnpm install @basestack/flags-wc
yarn add @basestack/flags-wc
bun add @basestack/flags-wc

Register components

Call registration early in your app bootstrap (browser only). This defines the custom elements feature-flag-preview-modal and feature-flag-feedback-modal.

src/main.ts
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.

index.html
<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

index.html
<!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

src/components/FeatureFlagsEntry.tsx
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

main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { registerFeatureFlagComponents } from "@basestack/flags-wc";

await registerFeatureFlagComponents();
createApp(App).mount("#app");
App.vue
<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:

main.ts
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.

AttributeTypeDefaultDescription
theme"light" | "dark""light"Visual theme.
api-endpointstringhttps://flags.basestack.co/api/v1/flags/previewAPI endpoint used to fetch flags.
project-keystring-Sends x-project-key header.
environment-keystring-Sends x-environment-key header.
storage-keystringbs-flags-preview-statelocalStorage key for toggle state.
openbooleanfalseControls modal visibility.
headingstring"Feature preview"Dialog title.
subtitlestring"Get early access to new features and let us know what you think."Supporting text below title.
close-labelstring"Close"Accessible label for close controls.
show-close-buttonbooleantrueShows/hides top-right close button.
loading-labelstring"Loading features..."Loading state text.
empty-labelstring"No feature flags available at the moment."Empty state text.
selection-promptstring"Choose a feature to view"Prompt when nothing selected.
selection-placeholderstring"Select a feature on the left to see its details."Details pane placeholder text.
enable-labelstring"Enable"Button text when selected flag is disabled.
enabled-labelstring"Disable"Button text when selected flag is enabled.
preview-badge-labelstring"Preview"Badge text for non-expiring flag.
expires-soon-labelstring"Expires soon"Badge text for expiring flag.
learn-more-labelstring"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

preview-modal-example.html
<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

event-listeners.ts
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-key is required.
  • At least one of project-key or environment-key should be provided for authenticated requests.

Attributes / properties

AttributeTypeDefaultDescription
theme"light" | "dark""light"Visual theme.
flag-keystring-Basestack flag key this feedback belongs to.
feature-namestring-Human-friendly feature label shown in header.
project-keystring-Sends x-project-key header.
environment-keystring-Sends x-environment-key header.
api-endpointstringhttps://flags-api.basestack.co/v1/flags/feedbackPOST endpoint for feedback.
openbooleanfalseControls modal visibility.
headingstring"Feedback"Dialog title.
mood-promptstring"How are you feeling after using this feature?"Label for mood selector.
rating-promptstring"How satisfied are you with this feature?"Label for rating selector.
feedback-labelstring"Share your feedback"Textarea label.
feedback-placeholderstring"Anything else you'd like to add?"Textarea placeholder.
submit-labelstring"Submit feedback"Submit button text.
privacy-policy-urlstringhttps://basestack.co/legal/privacyPrivacy policy link target.
privacy-policy-labelstring"To keep your data safe, avoid sharing personal or sensitive details."Privacy helper copy.
privacy-policy-link-labelstring"Learn more"Privacy link text.
close-labelstring"Close"Accessible label for close controls.
metadataRecord<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

feedback-modal-example.html
<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):

feedback-with-metadata.tsx
// 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" },
  }}
/>
feedback-with-metadata-vanilla.ts
// 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-endpoint is default and no project-key/environment-key is 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 flags array.
  • 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 to https://flags-api.basestack.co/v1/flags/feedback)
  • Headers:
    • Content-Type: application/json
    • x-project-key if project-key exists
    • x-environment-key if environment-key exists
  • 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:

headless-sdk.ts
import {
  FeatureFlagClient,
  fetchFeatureFlags,
  readSelectionState,
  writeSelectionState,
  toggleFlagState,
} from "@basestack/flags-wc";

FeatureFlagClient

FeatureFlagClient-example.ts
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, Escape key, 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.

wc.d.ts
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-key or environment-key to fetch real data.

Feedback submit fails

  • Ensure flag-key is present.
  • Provide at least one of project-key or environment-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-key and/or environment-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 FeatureFlagModalsProvider and useFeatureFlagModals().
  • Learn about flag management in the Basestack dashboard.

On this page