Files
superset2/docs/developer_docs/guidelines/frontend/component-style-guidelines.md
Evan Rusackas 10f71bdcd5 fix(docs): finish bare-relative link conversion + add lint guardrail
Copilot flagged two stragglers on editors.md where the previous
file-by-file conversion stopped halfway. Sweeping for the same
pattern across the active content tree found 76 bare relative
internal links total — 14 in this PR's already-modified files
(Copilot's two plus twelve more) and 62 in unchanged files.

Why the build doesn't catch this
─────────────────────────────────
`onBrokenLinks: 'throw'` (set in this PR) only validates *file-based*
markdown references — links whose URL ends in `.md` / `.mdx`. Those
go through Docusaurus's file resolver, which can prove the target
exists. Bare relative URL paths like `[Foo](../foo)` skip that
resolver entirely; Docusaurus emits them as raw hrefs. The browser
then resolves them against the *current* page URL, and for
trailing-slash routes that almost always lands in the wrong
directory. Page navigates client-side and 404s. The linkinator job
in CI *can* catch these, but it's `continue-on-error: true` so
findings are advisory.

What this commit does
──────────────────────
1. Fix all 76 bare relative internal links across the active docs
   tree by appending `.md` to each one (preserving anchors / query
   strings). All 76 targets resolved to real files; no link
   targets changed, only the form of the reference.

2. Fix the component-page generator. 54 of the 76 bare links lived
   in two auto-generated index files (`components/ui/index.mdx`
   and `components/design-system/index.mdx`). The next regeneration
   would have undone the manual fixes without this. The two
   emission sites in `generate-superset-components.mjs` now emit
   `.md`-suffixed links; comment at the call site explains why.

3. Add `docs/scripts/lint-docs-links.mjs` — fast source-level
   linter that scans `.md`/`.mdx` files under the active content
   trees (skipping `versioned_docs/` snapshots) and fails if it
   finds any markdown link whose URL starts with `./` or `../` and
   does not end in `.md`/`.mdx`. Excludes asset paths (.png,
   .json, etc.) and ignores fenced code blocks. Wired up as
   `yarn lint:docs-links`.

4. Add a `Lint docs links` step to `superset-docs-verify.yml`,
   running before the build step so PRs that introduce the pattern
   fail in seconds rather than at build-time / not at all. Blocking,
   not advisory — exactly the gap linkinator's `continue-on-error`
   leaves open.

Verified
────────
- `yarn lint:docs-links` exits 0 on the cleaned tree
- Re-introducing one bare link makes the linter report the exact
  file:line with the offending URL, exit code 1
- All 76 originally-flagged targets resolved to real `.md` / `.mdx`
  files; only the form of the reference changed
2026-05-13 20:17:46 -07:00

6.9 KiB

title, sidebar_position
title sidebar_position
Component Style Guidelines and Best Practices 2

Component Style Guidelines and Best Practices

This documentation illustrates how we approach component development in Superset and provides examples to help you in writing new components or updating existing ones by following our community-approved standards.

This guide is intended primarily for reusable components. Whenever possible, all new components should be designed with reusability in mind.

General Guidelines

  • We use Ant Design as our component library. Do not build a new component if Ant Design provides one but rather instead extend or customize what the library provides
  • Always style your component using Emotion and always prefer the theme variables whenever applicable. See: Emotion Styling Guidelines and Best Practices
  • All components should be made to be reusable whenever possible
  • All components should follow the structure and best practices as detailed below

Directory and component structure

superset-frontend/src/components
   {ComponentName}/
      index.tsx
      {ComponentName}.test.tsx
      {ComponentName}.stories.tsx

Components root directory: Components that are meant to be re-used across different parts of the application should go in the superset-frontend/src/components directory. Components that are meant to be specific for a single part of the application should be located in the nearest directory where the component is used, for example, superset-frontend/src/Explore/components

Exporting the component: All components within the superset-frontend/src/components directory should be exported from superset-frontend/src/components/index.ts to facilitate their imports by other components

Component directory name: Use PascalCase for the component directory name

Storybook: Components should come with a storybook file whenever applicable, with the following naming convention \{ComponentName\}.stories.tsx. More details about Storybook below

Unit and end-to-end tests: All components should come with unit tests using Jest and React Testing Library. The file name should follow this naming convention \{ComponentName\}.test.tsx. Read the Testing Guidelines and Best Practices for more details

Reference naming: Use PascalCase for React components and camelCase for component instances

BAD:

import mainNav from './MainNav';

GOOD:

import MainNav from './MainNav';

BAD:

const NavItem = <MainNav />;

GOOD:

const navItem = <MainNav />;

Component naming: Use the file name as the component name

BAD:

import MainNav from './MainNav/index';

GOOD:

import MainNav from './MainNav';

Props naming: Do not use DOM related props for different purposes

BAD:

<MainNav style="big" />

GOOD:

<MainNav variant="big" />

Importing dependencies: Only import what you need

BAD:

import * as React from "react";

GOOD:

import React, { useState } from "react";

Default VS named exports: As recommended by TypeScript, "If a module's primary purpose is to house one specific export, then you should consider exporting it as a default export. This makes both importing and actually using the import a little easier". If you're exporting multiple objects, use named exports instead.

As a default export

import MainNav from './MainNav';

As a named export

import { MainNav, SecondaryNav } from './Navbars';

ARIA roles: Always make sure you are writing accessible components by using the official ARIA roles

Use TypeScript

All components should be written in TypeScript and their extensions should be .ts or .tsx

type vs interface

Validate all props with the correct types. This replaces the need for a run-time validation as provided by the prop-types library.

type HeadingProps = {
  param: string;
}

export default function Heading({ children }: HeadingProps) {
  return <h2>{children}</h2>
}

Use type for your component props and state. Use interface when you want to enable declaration merging.

Define default values for non-required props

In order to improve the readability of your code and reduce assumptions, always add default values for non required props, when applicable, for example:

const applyDiscount = (price: number, discount = 0.05) => price * (1 - discount);

Functional components and Hooks

We prefer functional components and the usage of hooks over class components.

useState

Always explicitly declare the type unless the type can easily be assumed by the declaration.

const [customer, setCustomer] = useState<ICustomer | null>(null);

useReducer

Always prefer useReducer over useState when your state has complex logics.

useMemo and useCallback

Always memoize when your components take functions or complex objects as props to avoid unnecessary rerenders.

Custom hooks

All custom hooks should be located in the directory /src/hooks. Before creating a new custom hook, make sure that is not available in the existing custom hooks.

Storybook

Each component should come with its dedicated storybook file.

One component per story: Each storybook file should only contain one component unless substantially different variants are required

Component variants: If the component behavior is substantially different when certain props are used, it is best to separate the story into different types. See the superset-frontend/src/components/Select/Select.stories.tsx as an example.

Isolated state: The storybook should show how the component works in an isolated state and with as few dependencies as possible

Use args: It should be possible to test the component with every variant of the available props. We recommend using args