dio.la

Dani Guardiola’s blog

Jan 1, 1970January 1st, 1970 · 1 minute read · tweet

Compound components

Draft.

Namespaces

Flat

tsx
<Menu>
<MenuButton />
<MenuPopover>
<MenuItem />
<MenuItem />
</MenuPopover>
</Menu>

Dot

tsx
<Menu.Root>
<Menu.Button />
<Menu.Popover>
<Menu.Item />
<Menu.Item />
</Menu.Popover>
</Menu.Root>

Overloaded

tsx
<Menu>
<Menu.Button />
<Menu.Popover>
<Menu.Item />
<Menu.Item />
</Menu.Popover>
</Menu>

Exports

Named

tsx
export function Menu() {}
export function MenuButton() {}
// ...

Object

tsx
export const Menu = {
Root: function MenuRoot() {},
Button: function MenuButton() {},
// ...
};

Note: export default {} is also possible, but not ideal.

Overloaded

tsx
export function Menu() {}
Menu.Button = function MenuButton() {};
// ...

Note: export default function ... is also possible, but not ideal.

Imports

Named

tsx
import { Menu, MenuButton } from "ui-package";
<Menu>
<MenuButton />
{/* ... */}
</Menu>;

Local "as"

tsx
import * as Menu from "ui-package/menu";
<Menu.Root>
<Menu.Button />
{/* ... */}
</Menu.Root>;

Global "as"

tsx
import * as UI from "ui-package";
<UI.Menu>
<UI.MenuButton />
{/* ... */}
</UI.Menu>;

Options

Flat + named export

tsx
// Menu.tsx
export function Menu() {}
export function MenuButton() {}

Pros and cons:

ScoreDescriptionNotes
✅Tree-shakableIn all import styles, most bundler tools will tree-shake unused component parts.
✅Auto-importsIDEs will automatically import the components (or parts) you use.
❌Component autocompleteAt most, you can start typing the name of the component (e.g. "Menu") to filter out other suggestions.
🟠Library autocompleteThe library namespace contains all parts of all components, making it hard to see available components at a glance.

Supports:

tsx
// named import
import { Menu, MenuButton } from "ui-package"; // or "ui-package/menu"
<Menu>
<MenuButton />
</Menu>;
tsx
// global "as" import
import * as UI from "ui-package";
<UI.Menu>
<UI.MenuButton />
</UI.Menu>;

Also this (but ew):

tsx
// local "as" import
import * as Menu from "ui-package/menu";
<Menu.Menu>
<Menu.MenuButton />
</Menu.Menu>;

Dot + named export

ScoreDescriptionNotes
✅Tree-shakableThis is the only option for dot namespacing that is tree-shakable.
❌Auto-importsUsers need to manually write the local "as" import every time.
✅Component autocompleteSuggestions for all parts (and only the parts) will show up after the dot.
❌Library autocompleteIt's not possible to re-export at the top level since that'd cause conflicts (e.g. multiple Root parts of different components).
tsx
// Menu.tsx
export function Root() {}
export function Button() {}

Supports:

tsx
// local "as" import
import * as Menu from "ui-package/menu";
<Menu.Root>
<Menu.Button />
</Menu.Root>;

Dot + object export

tsx
// Menu.tsx
export const Menu = {
Root: function MenuRoot() {},
Button: function MenuButton() {},
};
ScoreDescriptionNotes
❌Tree-shakableSince it's exported as a single object, it's not tree-shakable.
✅Auto-importsIDEs will automatically import the components you use.
✅Component autocompleteSuggestions for all parts (and only the parts) will show up after the dot.
✅Library autocompleteOnly the components (objects) will show up in the library autocomplete (and not their parts).

Supports:

tsx
// named import
import { Menu } from "ui-package"; // or "ui-package/menu"
<Menu.Root>
<Menu.Button />
</Menu.Root>;
tsx
// global "as" import
import * as UI from "ui-package";
<UI.Menu.Root>
<UI.Menu.Button />
</UI.Menu.Root>;

Also this (but ew):

tsx
// local "as" import
import * as Menu from "ui-package/menu";
<Menu.Menu.Root>
<Menu.Menu.Button />
</Menu.Menu.Root>;

Overloaded

tsx
// Menu.tsx
export function Menu() {}
Menu.Button = function MenuButton() {};
ScoreDescriptionNotes
❌Tree-shakableSince it's exported as a single function with properties, it's not tree-shakable.
✅Auto-importsIDEs will automatically import the components you use.
🟠Component autocompleteSuggestions for all parts will show up after the dot. Unfortunately, so will the function prototype methods and properties (apply, call, etc).
✅Library autocompleteOnly the root components will show up in the library autocomplete (and not their parts).

Supports:

tsx
// named import
import { Menu } from "ui-package"; // or "ui-package/menu"
<Menu>
<Menu.Button />
</Menu>;
tsx
// global "as" import
import * as UI from "ui-package";
<UI.Menu>
<UI.Menu.Button />
</UI.Menu>;

Also this (but ew):

tsx
// local "as" import
import * as Menu from "ui-package/menu";
<Menu.Menu>
<Menu.Menu.Button />
</Menu.Menu>;

Keep up with my stuff. Zero spam.

You're looking at my drafts!

Visit the main site: dio.la