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
onantd
, 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:
- Create a new package:
@superset-ui/core/components
Collocated with:
@superset-ui/core/theme
- 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
- Leave app-specific components in
src/components/
if they: - Import from other parts of the application (e.g.
src/views
) -
Are unlikely to be useful outside the main app
-
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.
- 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.