Skip to content

Component Import Patterns Guide

This guide defines the component layering system and import patterns for the CloudAlt monorepo. The system follows a three-tier architecture: Core → Division → App, with clear rules for overrides and promotion workflows.

Location: packages/ui, packages/ui-web
Import: @cloudalt-frontend/ui, @cloudalt-frontend/ui-web
Depends on: Foundation packages only (theme, icons, common)
Tags: layer:core-ui, scope:shared

Purpose: Cross-app primitives used everywhere—buttons, inputs, typography, layout components, icons.

Examples:

import { Button } from '@cloudalt-frontend/ui-web';
import { Typography } from '@cloudalt-frontend/ui';
import { Icon } from '@cloudalt-frontend/icons';

When to use: Components with zero brand-specific logic, used across multiple divisions.


Location: packages/ui-<division>
Import: @cloudalt-frontend/ui-<division>
Depends on: Core packages only
Tags: layer:division-ui, division:<name>

Available Divisions:

  • ui-stay-match – Stay Match family (Pink Guest, Orange Guest, Rainbow Host)
  • ui-altfinder – AltFinder family (AltFinder, Orange Finder, Pink Finder)
  • ui-roommate – Roommate family (Roommate Guru, Roommate Help, Roommate Works)
  • ui-bonjour-services – Bonjour Services family (Bonjour Locker, Bonjour IT, Lingua IT)
  • ui-greenhost – Greenhost
  • ui-homestay – Homestay family
  • ui-guestroom – Guestroom family
  • ui-pride-city – Pride City
  • ui-room-lgbt – Room LGBT family
  • ui-hostguest – Hostguest family

Purpose: Shared patterns within an app family—hero sections, CTAs, navigation menus, auth flows, booking components.

Examples:

import { Hero } from '@cloudalt-frontend/ui-stay-match';
import { BookingCard } from '@cloudalt-frontend/ui-stay-match';
import { ProfileHeader } from '@cloudalt-frontend/ui-roommate';

When to use: Components shared by 2+ apps within the same division, with division-specific business logic or styling.


Location: apps/<division>/<app>/<platform>/src/components/
Import: @app/components (local alias)
Depends on: Division and Core packages
Tags: layer:app, division:<name>, platform:web|mobile

Purpose: Last-mile, app-only customizations or branded wrappers.

Examples:

// Inside apps/stay_match/pinkguest/web/src/
import { Hero } from '@app/components/Hero'; // App-specific override
import { BookingCard } from '@cloudalt-frontend/ui-stay-match'; // Division default
import { Button } from '@cloudalt-frontend/ui-web'; // Core fallback

When to use: One-off components or variants that don’t (yet) make sense to share, or temporary experimental features.


Components should be imported in this order of preference:

  1. App layer first (if override exists): @app/components/MyComponent
  2. Division layer second (family-specific): @cloudalt-frontend/ui-<division>
  3. Core layer last (generic): @cloudalt-frontend/ui-web or @cloudalt-frontend/ui
apps/stay_match/pinkguest/web/src/pages/Home.tsx
// ✅ Good: Check app layer first, fallback to division, then core
import { Hero } from '@app/components'; // App-specific Hero override
import { BookingCard } from '@cloudalt-frontend/ui-stay-match'; // Division component
import { Button } from '@cloudalt-frontend/ui-web'; // Core component
// ❌ Bad: Skipping layers or importing from wrong division
import { Hero } from '@cloudalt-frontend/ui-web'; // Wrong—use division or app
import { ProfileCard } from '@cloudalt-frontend/ui-roommate'; // Wrong—cross-division import

Each app needs a local @app/components alias in its tsconfig.json:

File: apps/stay_match/pinkguest/web/tsconfig.json

{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"paths": {
"@app/components": ["apps/stay_match/pinkguest/web/src/components/index.ts"],
"@app/components/*": ["apps/stay_match/pinkguest/web/src/components/*"]
}
},
"files": [],
"include": ["src/**/*"],
"references": []
}

File: apps/stay_match/pinkguest/mobile/tsconfig.json

{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"paths": {
"@app/components": ["apps/stay_match/pinkguest/mobile/src/components/index.ts"],
"@app/components/*": ["apps/stay_match/pinkguest/mobile/src/components/*"]
}
},
"files": [],
"include": ["src/**/*"],
"references": []
}

Create a barrel export at src/components/index.ts:

apps/stay_match/pinkguest/web/src/components/index.ts
export { Hero } from './Hero';
export { CustomBookingFlow } from './CustomBookingFlow';
// Export other app-specific overrides here

You need an app-level override when:

  • A division component needs brand-specific tweaks for one app only
  • You’re experimenting with a new pattern before committing to division-wide adoption
  • The app has unique business logic that doesn’t belong in the division layer

Location: apps/<division>/<app>/<platform>/src/components/MyComponent.tsx

Pattern 1: Wrap and Extend (Recommended)

apps/stay_match/pinkguest/web/src/components/Hero.tsx
import { Hero as DivisionHero, HeroProps } from '@cloudalt-frontend/ui-stay-match';
export function Hero(props: HeroProps) {
return (
<div className="pinkguest-custom-wrapper">
<DivisionHero {...props} />
{/* Add app-specific content */}
<div className="custom-badge">Pink Guest Exclusive</div>
</div>
);
}

Pattern 2: Full Replacement (When needed)

apps/stay_match/pinkguest/web/src/components/Hero.tsx
import { HeroProps } from '@cloudalt-frontend/ui-stay-match'; // Keep same interface
export function Hero(props: HeroProps) {
// Completely custom implementation
return (
<section className="custom-hero">
<h1>{props.title}</h1>
{/* Custom logic */}
</section>
);
}
apps/stay_match/pinkguest/web/src/components/index.ts
export { Hero } from './Hero';
apps/stay_match/pinkguest/web/src/pages/Home.tsx
import { Hero } from '@app/components'; // Uses your override
function HomePage() {
return <Hero title="Welcome to Pink Guest" />;
}

When an app-level component proves useful across the division or monorepo:

  1. Move the file:

    Terminal window
    cp apps/stay_match/pinkguest/web/src/components/Hero.tsx \
    packages/ui-stay-match/src/components/Hero/Hero.tsx
  2. Update division exports:

    packages/ui-stay-match/src/index.ts
    export { Hero } from './components/Hero/Hero';
    export type { HeroProps } from './components/Hero/Hero';
  3. Update app imports:

    apps/stay_match/pinkguest/web/src/pages/Home.tsx
    - import { Hero } from '@app/components';
    + import { Hero } from '@cloudalt-frontend/ui-stay-match';
  4. Remove app override:

    Terminal window
    rm apps/stay_match/pinkguest/web/src/components/Hero.tsx
    # Update components/index.ts to remove export
  1. Generalize the component (remove division-specific logic, use tokens for styling)

  2. Move to core:

    Terminal window
    cp packages/ui-stay-match/src/components/Hero/Hero.tsx \
    packages/ui-web/src/components/Hero/Hero.tsx
  3. Update core exports:

    packages/ui-web/src/index.ts
    export { Hero } from './components/Hero/Hero';
    export type { HeroProps } from './components/Hero/Hero';
  4. Update all apps to import from core:

    - import { Hero } from '@cloudalt-frontend/ui-stay-match';
    + import { Hero } from '@cloudalt-frontend/ui-web';
  5. Remove division version (after all apps updated)


Storybook has a Division and App selector in the toolbar:

  1. Open Storybook: yarn nx storybook storybook
  2. Select your division from the toolbar (e.g., “Stay Match”)
  3. Select your app (e.g., “Pink Guest”)
  4. Components will render with the correct brand tokens and theme

This lets you preview how division components look in each app context before creating overrides.


The layering rules are enforced via Nx tags and depConstraints:

  • Apps (layer:app) → can depend on layer:division-ui and layer:core-ui
  • Division UI (layer:division-ui) → can depend on layer:core-ui only
  • Core UI (layer:core-ui) → no dependencies on other UI layers
  • Cross-division imports are blocked (e.g., Stay Match UI can’t import Roommate UI)

To check boundaries:

Terminal window
yarn nx lint

LayerLocationImportDepends OnWhen to Use
Appapps/.../src/components/@app/componentsDivision + CoreRare, app-only overrides
Divisionpackages/ui-<division>/@cloudalt-frontend/ui-<division>Core onlyShared within app family
Corepackages/ui, packages/ui-web@cloudalt-frontend/ui-webFoundationCross-app primitives

apps/stay_match/pinkguest/web/src/components/BookingCard.tsx
import { BookingCard as DivisionCard, BookingCardProps } from '@cloudalt-frontend/ui-stay-match';
export function BookingCard(props: BookingCardProps) {
return (
<DivisionCard
{...props}
className={`${props.className} pinkguest-booking-style`}
brandBadge="Pink Guest Verified"
/>
);
}
// Prefer: Use design tokens (no override needed)
<Hero title="Welcome" className="bg-primary text-primary-foreground" />
// Instead of: Creating app override just for colors
apps/stay_match/pinkguest/web/src/components/Hero.tsx
import { Hero as DivisionHero, HeroProps } from '@cloudalt-frontend/ui-stay-match';
export function Hero(props: HeroProps) {
const isPinkGuestUser = useAuth().isPinkGuest; // App-specific logic
return (
<DivisionHero {...props}>
{isPinkGuestUser && <PinkGuestPromoBar />}
</DivisionHero>
);
}

Q: Can I import from a different division’s UI package?
A: No. Cross-division imports are blocked by Nx boundaries. If you need a component from another division, promote it to Core first.

Q: How do I know when to promote a component?
A: If 2+ apps in the same division use it → promote to division. If 2+ divisions use it → promote to core.

Q: Can I have app-specific stories in Storybook?
A: Yes, but keep them opt-in. Most stories should import from division/core packages. Use the toolbar to preview in different app contexts.

Q: What if an override needs to change the component API?
A: Keep the same interface initially (for easy promotion). If the API must change, document it clearly and consider if it should be a new component instead.

Q: How do I handle mobile-specific overrides?
A: Same pattern—use @app/components alias in mobile app tsconfig. Division packages can also export platform-specific variants.


  • Add @app/components alias to your app’s tsconfig.json
  • Create src/components/index.ts barrel export
  • Try creating a simple override component
  • Use Storybook toolbar to preview in different app contexts
  • Run yarn nx lint to validate module boundaries

For generator-based promotion, see: Component Promotion Guide (coming soon)