Skip to main content

Component System Architecture

Overview

The component system provides headless, unstyled building blocks that AI agents assemble and style into complete directory websites. Components are composable, accessible, and framework-flexible.

Design Principles

  1. Headless — No visual styling by default. Components render semantic HTML with data attributes for styling hooks.
  2. Composable — Small, single-purpose components that compose together.
  3. Accessible — ARIA attributes, keyboard navigation, screen reader support built in.
  4. Framework-flexible — Astro components for static, Preact for interactive islands.
  5. Prop-driven — All behavior controlled via props, no hidden state.

Component Categories

Static Components (Astro .astro)

These render at build time with zero client-side JavaScript:

ComponentPurposeProps
ItemCardDisplays a single item summaryitem: ItemData
ItemGridGrid layout for item cardsitems: ItemData[], columns?: number
ItemListList layout for item cardsitems: ItemData[]
ItemDetailFull item detail viewitem: ItemData
CategoryListList of categoriescategories: CategoryWithCount[]
CategoryBadgeSingle category badgecategory: CategoryData
TagListList of tagstags: TagWithCount[]
TagBadgeSingle tag badgetag: TagData
CollectionCardCollection summary cardcollection: CollectionData
BreadcrumbsBreadcrumb navigationitems: BreadcrumbItem[]
PaginationPage navigation controlscurrentPage, totalPages, baseUrl
SiteHeaderSite header with navconfig: SiteConfig, nav?: NavItem[]
SiteFooterSite footerconfig: SiteConfig
HeroHero sectiontitle, subtitle?, cta?
EmptyStateNo results messagemessage: string
ComparisonTableSide-by-side comparisoncomparison: ComparisonData
SEOMeta tags, JSON-LD, Open Graphtitle, description, config, ...
AnalyticsScriptAnalytics provider script injectionconfig: AnalyticsConfig
FeaturedBadgeFeatured item badge indicatortext?: string
FeaturedSectionFeatured items highlight sectionitems: ItemData[], title?: string
ItemContentRenders item markdown/HTML contenthtml: string
ItemCTAItem call-to-action buttonurl: string, text?: string
ItemMetadataItem metadata display (date, category)item: ItemData
ShareButtonSocial/URL sharing buttonurl: string, title: string
SimilarItemsRelated/similar items sectionitems: ItemData[], title?: string

Interactive Components (Preact .tsx)

These hydrate as Astro islands for client-side interactivity:

ComponentPurposeHydration
SearchInputText search with debounceclient:load
FilterBarCategory/tag filter controlsclient:visible
SortSelectSort order dropdownclient:visible
BackToTopScroll-to-top buttonclient:load
ThemeToggleDark/light mode toggleclient:load
ItemBrowserFull-featured item grid with search, filters, sortclient:load
LayoutSwitcherGrid/list view toggleclient:load
MobileMenuResponsive hamburger navigationclient:load

Component Props Pattern

All components follow a consistent props pattern:

/** Base props all components accept */
interface BaseComponentProps {
/** HTML class attribute for custom styling */
class?: string;
/** Data attributes for styling hooks */
'data-variant'?: string;
/** Additional HTML attributes */
[key: `data-${string}`]: string | undefined;
}

/** Example: ItemCard props */
interface ItemCardProps extends BaseComponentProps {
/** The item to display */
item: ItemData;
/** Whether to show category badge */
showCategory?: boolean;
/** Whether to show tags */
showTags?: boolean;
/** Whether to show the item description */
showDescription?: boolean;
// Actions are provided via Astro's <slot name="actions"> mechanism, not as a prop
}

Rendering Pattern

Components render semantic HTML with data attributes:

---
// ItemCard.astro
import type { ItemCardProps } from './types';
const { item, showCategory = true, showTags = true, class: className, ...attrs } = Astro.props as ItemCardProps;
---
<article
class:list={['item-card', className]}
data-component="item-card"
data-featured={item.featured ? 'true' : undefined}
{...attrs}
>
{item.icon_url && (
<div data-part="icon">
<img src={item.icon_url} alt="" width="48" height="48" loading="lazy" />
</div>
)}
<div data-part="content">
<h3 data-part="title">
<a href={`/item/${item.slug}`}>{item.name}</a>
</h3>
{item.description && (
<p data-part="description">{item.description}</p>
)}
</div>
{showCategory && item.category && (
<div data-part="category">
<slot name="category" />
</div>
)}
{showTags && item.tags?.length > 0 && (
<div data-part="tags">
<slot name="tags" />
</div>
)}
<div data-part="actions">
<slot name="actions" />
</div>
</article>

Styling Strategy

Components ship with no CSS. AI agents apply styling using:

<ItemCard
item={item}
class="rounded-lg border border-gray-200 p-4 hover:shadow-lg transition-shadow"
/>

Option 2: CSS targeting data attributes

[data-component="item-card"] {
border-radius: 0.5rem;
border: 1px solid #e5e7eb;
padding: 1rem;
}
[data-component="item-card"][data-featured="true"] {
border-color: gold;
}
[data-component="item-card"] [data-part="title"] {
font-size: 1.25rem;
font-weight: 600;
}

Option 3: CSS Modules

/* ItemCard.module.css */
.card { ... }
.card[data-featured="true"] { ... }
.title { ... }

Slots and Composition

Astro components use named slots for composition:

<ItemCard item={item}>
<CategoryBadge slot="category" category={category} />
<Fragment slot="tags">
{item.tags.map(tag => <TagBadge tag={tag} />)}
</Fragment>
<a slot="actions" href={item.source_url}>Visit Website</a>
</ItemCard>

Island Architecture

Interactive components use Astro's island architecture:

---
// Page using islands
import SearchInput from '../components/SearchInput';
import FilterBar from '../components/FilterBar';
import ItemGrid from '../components/ItemGrid.astro';
---
<div>
<!-- Hydrates immediately for instant search -->
<SearchInput client:load placeholder="Search items..." />

<!-- Hydrates when visible in viewport -->
<FilterBar client:visible categories={categories} tags={tags} />

<!-- Static, no JavaScript -->
<ItemGrid items={items} />
</div>

Component Package Structure

packages/ui/
├── src/
│ ├── index.ts — Barrel export
│ ├── types.ts — All component prop types
│ ├── astro/ — Astro components (.astro) — 25 components
│ │ ├── AnalyticsScript.astro
│ │ ├── Breadcrumbs.astro
│ │ ├── CategoryBadge.astro
│ │ ├── CategoryList.astro
│ │ ├── CollectionCard.astro
│ │ ├── ComparisonTable.astro
│ │ ├── EmptyState.astro
│ │ ├── FeaturedBadge.astro
│ │ ├── FeaturedSection.astro
│ │ ├── Hero.astro
│ │ ├── ItemCard.astro
│ │ ├── ItemContent.astro
│ │ ├── ItemCTA.astro
│ │ ├── ItemDetail.astro
│ │ ├── ItemGrid.astro
│ │ ├── ItemList.astro
│ │ ├── ItemMetadata.astro
│ │ ├── Pagination.astro
│ │ ├── SEO.astro
│ │ ├── ShareButton.astro
│ │ ├── SimilarItems.astro
│ │ ├── SiteFooter.astro
│ │ ├── SiteHeader.astro
│ │ ├── TagBadge.astro
│ │ └── TagList.astro
│ └── preact/ — Preact interactive components (.tsx) — 8 components
│ ├── BackToTop.tsx
│ ├── FilterBar.tsx
│ ├── ItemBrowser.tsx
│ ├── LayoutSwitcher.tsx
│ ├── MobileMenu.tsx
│ ├── SearchInput.tsx
│ ├── SortSelect.tsx
│ └── ThemeToggle.tsx
├── package.json
└── tsconfig.json