## Prompt

Is there a best practice for providing ui components to complex web application ui environments integrating multiple user interfaces and interface components?

## Exploration

Yes. For a complex web application that needs to integrate multiple user interfaces, teams, frameworks, or independently evolving components, there is a fairly mature set of practices—but not one single pattern. The useful way to think about it is as an **integration architecture with several layers**, rather than merely as a component library.

A good default architecture today looks roughly like this:

```text
Application Shell
│
├── UX / Design System
│   ├── design tokens
│   ├── primitive components
│   └── interaction conventions
│
├── UI Integration Contract
│   ├── inputs / properties
│   ├── outputs / events
│   ├── commands
│   ├── lifecycle
│   └── capability metadata
│
├── Application Components
│   ├── search-ui
│   ├── customer-editor
│   ├── document-viewer
│   └── reporting-ui
│
├── Composition Layer
│   ├── routing
│   ├── layout
│   ├── authentication context
│   ├── shared state
│   └── event/message bus
│
└── Runtime Integration
    ├── ordinary packages
    ├── Web Components
    ├── module federation
    ├── micro-frontends
    └── iframe/process isolation when necessary
```

The important distinction is that these layers solve **different problems**.

### 1. Use a design system for visual and interaction coherence

A design system should define things such as typography, spacing, colors, accessibility conventions, buttons, dialogs, forms, navigation, tables, notifications, etc.

But a design system alone is not enough. It tells you that all applications should use the same sort of `<Button>`; it does not tell the host application how a `CustomerEditor` announces that a customer was saved.

I would therefore keep **design-system contracts** separate from **application-component contracts**.

---

### 2. Prefer framework-neutral contracts at integration boundaries

Inside an application, React-to-React or Vue-to-Vue component APIs are perfectly reasonable.

Across independently developed systems, however, tying the integration contract to:

```tsx
<CustomerEditor
  customer={customer}
  onSaved={...}
/>
```

creates fairly strong coupling to React, its version, its rendering model, and often its state-management conventions.

A more durable boundary is something conceptually like:

```html
<customer-editor customer-id="4711">
</customer-editor>
```

with:

```text
attributes / properties  -> configuration and input
DOM events               -> observations/output
methods                  -> explicit commands
slots                    -> host-provided presentation
CSS custom properties    -> theming
```

That is exactly the architectural territory that **Web Components** were designed for. Custom Elements give you browser-native component boundaries, while Shadow DOM can provide DOM/CSS isolation. :chatgpt-content-reference{index="0"}

This doesn't imply that you have to write applications without React, Vue, Svelte, Lit, etc. A Web Component can simply be the **published interoperability surface** around an implementation written using another technology.

---

### 3. Treat coarse-grained UI capabilities differently from widgets

There is an important granularity boundary.

Things such as:

```text
Button
Checkbox
DatePicker
Popover
DataGrid
```

are design-system components.

Things such as:

```text
CustomerSearch
InvoiceEditor
DocumentViewer
UserAdministration
OrderWorkbench
```

are better thought of as **UI capabilities**.

The latter should have explicit contracts and should often be independently versionable.

For example:

```yaml
component: customer-editor
version: 2.3

requires:
  - identity.user
  - customer.read
  - customer.write

inputs:
  customerId: string
  mode: [view, edit]

events:
  - customer-selected
  - customer-updated
  - editing-cancelled

commands:
  - reload
  - focus
  - reset

slots:
  - toolbar
  - footer

context:
  - locale
  - theme
  - tenant
```

This starts to look less like a component API and more like a **small interface definition language for UI capabilities**. For large systems, I think this is the more useful abstraction.

---

### 4. Separate *composition* from *implementation*

A common architectural mistake is letting every component know how the application fits together.

Instead:

```text
Component:
"I can edit a customer."

Shell:
"You appear here,
for this tenant,
with this identity,
at this route,
beside these other capabilities."
```

The application shell should generally own:

- routing
- navigation
- page layout
- authentication/session context
- authorization decisions
- locale
- theme
- notifications
- observability
- feature flags
- capability discovery
- failure boundaries

Individual UI capabilities should own their domain interaction.

This is very similar to the distinction between a **control plane** and an **execution plane**.

---

### 5. Use events rather than shared mutable state across boundaries

Within one frontend, a Redux/Zustand/Pinia/etc. store can be useful.

Across independently evolving UI components, a giant shared store tends to become an implicit API.

Instead of:

```text
component A
    ↓
global state object
    ↑
component B
```

prefer:

```text
CustomerSearch
     │
     │ customer-selected
     ↓
Application Shell
     │
     │ selectedCustomer=123
     ↓
CustomerEditor
```

or an event/message mechanism where appropriate.

The semantic distinction is useful:

```text
properties     desired state/configuration
events         things that happened
commands       explicit requests
queries        information requests
```

That simple vocabulary dramatically improves UI contracts.

---

### 6. Use isolation deliberately rather than universally

There is a spectrum:

```text
Shared source/component
        ↓
npm package
        ↓
Web Component
        ↓
runtime remote module
        ↓
micro-frontend
        ↓
iframe
```

Each step provides more autonomy/isolation, but also introduces more integration cost.

**Web Components** are particularly attractive for reusable framework-independent components because Shadow DOM can prevent CSS and DOM internals from colliding with their host application. :chatgpt-content-reference{index="1"}

For larger independently deployed application fragments, **Module Federation** is another established technique. Webpack explicitly describes its purpose as allowing separate builds to expose and consume modules at runtime, including the micro-frontend use case. :chatgpt-content-reference{index="2"}

I would not make either the universal solution.

A useful rule is:

> Use the weakest isolation mechanism that provides sufficient organizational and technical independence.

---

### 7. Avoid the "micro-frontends everywhere" trap

Micro-frontends are useful when there is genuinely independent ownership:

```text
Team A              Team B              Team C
Billing             CRM                 Analytics
release A           release B           release C
      \                 |                 /
               Application Shell
```

They are much less attractive when one team owns everything. You otherwise pay for:

- duplicate dependencies
- runtime orchestration
- version incompatibilities
- inconsistent UX
- authentication propagation
- routing complexity
- performance overhead
- distributed debugging

without getting much organizational independence in return.

---

### 8. Make context explicit

One pattern I particularly recommend for complex platforms is a small, stable **host context**:

```ts
interface UIHostContext {
    identity
    tenant
    locale
    theme
    permissions
    navigation
    notifications
    telemetry
}
```

But don't expose an enormous application object.

Instead expose capabilities:

```text
context.identity.currentUser()
context.navigation.open(...)
context.notifications.publish(...)
context.telemetry.record(...)
```

This gives embedded components access to platform services without allowing them to become coupled to the entire host implementation.

---

### 9. Explicitly distinguish several kinds of compatibility

A UI component can be technically loadable and nevertheless incompatible.

I would track at least:

```text
Protocol compatibility
    Can host and component communicate?

Semantic compatibility
    Do "customer-selected" and "customerId" mean the same thing?

Visual compatibility
    Does it fit the design system?

Interaction compatibility
    Keyboard, focus, navigation, modal behavior, etc.

Security compatibility
    Does it respect identity/authorization boundaries?

Lifecycle compatibility
    Can it mount, unmount, reconnect and upgrade safely?

Operational compatibility
    Can it be observed, debugged and degraded independently?
```

This becomes important once components are reused across several applications.

---

## The architecture I would favour

For a modern platform with many interfaces, I would probably choose this combination:

```text
                         UI PLATFORM

                         ┌──────────┐
                         │ App Shell │
                         └────┬─────┘
                              │
             ┌────────────────┼────────────────┐
             │                │                │
         routing          UI context       capability
         layout           identity          registry
         navigation       theme             discovery
                          locale
                              │
                 ┌────────────┴────────────┐
                 │ UI Component Contract   │
                 │                         │
                 │ props / events          │
                 │ commands / slots        │
                 │ lifecycle / metadata    │
                 └────────────┬────────────┘
                              │
        ┌─────────────────────┼────────────────────┐
        │                     │                    │
     Native               Web Component       Microfrontend
    component              boundary             boundary
        │                     │                    │
      React                React/Vue/etc       independently
       Vue                   internally          deployed
      Svelte
```

Underneath all of this sits a shared:

```text
Design Language
      ↓
Design Tokens
      ↓
Primitive Component Library
      ↓
Accessibility & interaction rules
```

The browser platform is becoming an increasingly credible basis for that middle interoperability layer. Web Components provide native reusable elements; Shadow DOM supplies encapsulation; slots provide host/component composition; and scoped custom-element registries address one of the historical problems of large compositions—global custom-element naming collisions. :chatgpt-content-reference{index="3"}

### One additional concept I think is worth making explicit

Given the way you tend to approach interface architecture, I would distinguish between a **UI component** and an **Interface Capability**.

An Interface Capability would be:

> A self-describing unit of human-system interaction that exposes a stable semantic contract independently of its rendering implementation.

For example:

```text
InterfaceCapability: DocumentViewer

provides:
  view-document
  browse-pages
  download-document

accepts:
  document-reference
  page-reference

emits:
  page-changed
  document-opened
  download-requested

requires:
  document.read

presentation:
  embeddable
  fullscreen

implementations:
  web-component
  react-component
  standalone-webapp
```

That abstraction scales much better than thinking exclusively in terms of React components or micro-frontends. A component becomes **one implementation of an interface capability**.

And that in turn gives you a rather elegant architecture:

```text
Interface Capability
        ↓
semantic contract
        ↓
one or more UI implementations
        ↓
composition/runtime adapter
        ↓
host application
```

This is close to what I would regard as the current best-practice direction for a genuinely complex multi-interface platform: **design system below, semantic capability contracts in the middle, composition shell above, and implementation technology deliberately hidden behind the boundary.**