Motivation

The src/components/ directory in Superset has grown into an unstructured mix of UI primitives, AntD wrappers, app-specific components, and deeply coupled logic. This creates several problems:

  • Hard to reuse components in plugins, packages, and future SDKs. This forces peerDependencies on antd, which is suboptimal. Packages/plugins should NOT depend on the main app!
  • No clear boundary between reusable components and app-specific UI.
  • Difficult navigation and discoverability due to inconsistent folder structures.

We propose to extract a clean, flat, reusable set of core UI components into a dedicated package, collocated with theming utilities, to better support modular development.

Proposed Change

We propose to:

  1. Create a new package:

@superset-ui/core/components

Collocated with:

@superset-ui/core/theme

  1. Move reusable components (especially AntD wrappers and UI primitives) from src/components/ into this package.

Criteria for inclusion: - Component does not depend on app-specific logic (Redux, routes, etc.) - Component wraps or styles base UI elements (AntD, styled-components, etc.) - Component is likely to be reused across plugins, embedded apps, or external SDKs

  1. Leave app-specific components in src/components/ if they:
  2. Import from other parts of the application (e.g. src/views)
  3. Are unlikely to be useful outside the main app

  4. Flat export structure:

All components and types are exported from a single unified namespace: ts import { Button, Tabs, type TabsProps } from '@superset-ui/core/components';

We intentionally avoid nested folders like layout/Tabs or inputs/Button to keep things flat, discoverable, and easier to manage. If name collisions arise, we will handle them explicitly via namespacing. Tree specific DataNode becomes TreeDataNode for clarity and avoiding potential collisions.

  1. Initial integration via theming branch:

The Theming branch will lead this migration to minimize merge conflicts. Other efforts (e.g., plugin SDK) will coordinate to ensure parallel needs are addressed cleanly. We can coordinate with the Plugins / Extensions effort.

New or Changed Public Interfaces

  • New package: @superset-ui/core/components
  • New top-level exports for all reusable components and types
  • Existing imports from src/components will be migrated to the new namespace as part of the rollout
  • Internal theming APIs from @superset-ui/core/theme will become shared as part of this effort

New dependencies

No new dependencies expected initially. If any are introduced (e.g., utility libraries or a component doc generator), they will be actively maintained and Apache-compatible.

Other Superset packages in the ecosystem should not import antd directly, they should always use primitives from @superset-ui/core/components. When creating new native components, they are in charge of making those new components themable, ideally by building upon the antd-inherited components in the package.

Migration Plan and Compatibility

  • Migration will happen incrementally, led by the Theming branch. The best approach is likely to move the app-specific components (with Redux-type interaction, or importing components from outside src/components, ...) will be moved out of src/components, and a big replace-in-files will tackle the reassignment.
  • Existing files in src/components will be removed only after successful integration and usage from the new package
  • Imports within Superset will be updated to use the new @superset-ui/core/components paths
  • We may introduce codemods or linting rules to help enforce import consistency
  • New package builds and publication to npm will take place upon merging the Theming branch
  • alter/adapt the react-storybook(s) to point to this new package. We may want to ship a superset-ui/core-specific storybook that we can compose-into the main app's storybook

Rejected Alternatives

  • Keep everything in src/components/: Doesn’t support modularity or reuse
  • Introduce deep subfolder organization: Adds cognitive overhead and slows down navigation, especially in a large shared UI package
  • Split theming and components into separate packages: These two concerns are tightly coupled and should evolve together, at least for now

Comment From: amaannawab923

True This is a well-structured and thoughtful proposal that addresses real pain points in Superset’s UI architecture.

Extracting reusable, themable components into @superset-ui/core/components will make it easier to maintain . The flat export structure promotes discoverability and simplicity, and the inclusion criteria are pragmatic. Coordinating the migration via the Theming branch is smart, minimizing disruption. Overall, this change is a strong step toward a cleaner, more scalable frontend foundation, especially valuable for plugins, embedded apps, and future SDKs.

Currently we have a very tight dependancy on Antd and we have to construct components keeping antd in mind , this might help break that dependancy

@mistercrunch

Comment From: michael-s-molina

@mistercrunch Let's schedule a meeting to align this proposal with the work being made in the extensions project.

Comment From: mistercrunch

Totally worth meeting over this. Hoping you all haven't done too much already in this area since the Theming branch is heavily altering src/components and resolving merge conflicts would probably be hopeless.

Comment From: rusackas

My main head-scratcher here is about why we would want to stick more stuff in /core at all. I'd rather just have new packages, e.g. @superset-ui/components and @superset-ui/themes then they can be better versioned with SemVer.

Comment From: mistercrunch

Trying my hand at Mermaid for high-level package modeling, just food for thoughts. Also wondering how core should be structured, knowing that we need a shared place for the app and the sdk to source from.

graph TD
  subgraph core["Shared Primitives"]
    subgraph ui["UI"]
      components["components"]
      theme["theme"]
    end
    utils["utils"]
    misc["misc"]
  end

  app["Superset Frontend App"] --> core
  sdk["Extension SDK"] --> core
  storybook["React Storybook"] --> core

  docs["Superset Docs"] --> app
  docs --> storybook

  sdk_docs["SDK Docs"] --> sdk
  sdk_docs --> storybook

Comment From: mistercrunch

About all this and the recent community meeting, I worked with GPT at producing this ->

On Shared Primitives and API Boundaries

Following a recent community meeting, an important question came up around how we structure our frontend packages — specifically, which packages should exist, how they depend on each other, and what should be labeled as public vs private.

One of the key debates was around the bucket of shared primitives — a collection of utility functions, components, types, and hooks that are used across the Superset app and SDKs.

Here, I’ll make the case that this shared primitives package should be treated as private, and that each SDK should be in charge of deciding: - which primitives it wants to use internally, and - which it chooses to expose publicly as part of its API.


Why shared ≠ public

Just because a function or component is reused doesn't mean it should be exposed.

Making all shared primitives public: - Bloats the API surface — every internal helper becomes part of the public contract. - Creates upgrade friction — a breaking change in a low-level primitive could break all SDKs that import it directly. - Encourages misuse — consumers may depend on primitives not meant for external use. - Makes versioning harder — users can't easily tell which version of the primitives is compatible with which version of the SDK.


A better model: SDKs as curators

  • Each SDK should act as a curated public interface, exposing only what it wants to support and maintain.
  • Internally, SDKs are free to use any primitive from the shared package.
  • Each SDK declares which version of the primitives it depends on.
  • In the case of the extension SDK, it can also declare which app version(s) it’s compatible with.

This keeps each SDK self-standing, with clear boundaries and better long-term maintainability.


Examples of shared-but-private code

These are good candidates to share internally, but not expose: - Utility functions like formatTimeRange, parseSqlExpression, isFeatureEnabled - Low-level UI components like <Space />, <Timer />, <PermissionGate /> - Internal hooks like useDebouncedValue, usePrevious

These are useful building blocks for the app and SDKs, but they’re not meant to be part of the public API surface.


Final Thoughts

Let’s make sure we’re building modular, maintainable packages — not a sprawling framework. Shared primitives should power our ecosystem, but SDKs should be the ones deciding what’s public.

Keep the internals flexible. Keep the public APIs tight.

Comment From: mistercrunch

Did a quick review of how other extensible platforms structure this kind of thing:

How it's handled in other ecosystems:

  • iOS (Apple frameworks):
  • Tons of shared internal APIs and components, but only a curated set is made public.
  • SDKs are self-contained. Public APIs are documented, versioned, and stable.
  • Internal code evolves freely without exposing churn to SDK users.

  • Android (Jetpack/AOSP):

  • Separates *.internal packages from public APIs.
  • Public surface is curated and follows strict API review processes.
  • Individual SDKs/libraries are self-contained and versioned independently.

  • VS Code (extension platform):

  • Core uses shared primitives internally, but only exposes a controlled API via the vscode module.
  • Extensions rely only on that documented interface — no access to internals.
  • Public API surface is semver-managed and backward compatible.

Takeaway:

Internal reuse is common. Public exposure is deliberate and minimal.
Each SDK should curate its own public API, and shared primitives should remain internal unless there's a strong reason to promote them.

This strengthens the case that shared ≠ public, and that SDKs should act as intentional gatekeepers, just like in mature platforms.