Skip to content

Architecture & Philosophy

Flexible usage philosophy

Katalyst is designed to be adopted incrementally. You are not required to use every architectural pattern it provides.

Three ways to use Katalyst

  1. Full behavioral framework (recommended for teams building complex admin surfaces)

    • Use the complete architecture: DI, Clean Architecture layers, shared primitives
    • Best for: large teams, long-lived products, apps with many similar screens
  2. UI component system only

    • Use only the shadcn-style UI primitives and shared components
    • Skip DI, module structure, and behavioral hooks
    • Best for: smaller projects, rapid prototypes, teams with existing architecture
  3. Starting point to adapt freely

    • Fork the structure and modify it to fit your needs
    • Remove or replace any layer that doesn't serve you
    • Best for: teams with strong opinions on architecture

What's optional

ElementPurposeCan skip if...
Dependency InjectionTestability, loose couplingYou prefer simpler imports or don't need DI
Clean Architecture layersSeparation of concernsYour app is small or you have a different structure
Module structureFeature isolationYou prefer flat or different organization
Behavioral hooksConsistent interactionsYou only need UI components
Infrastructure servicesCross-cutting concernsYou have existing solutions

Gradual adoption path

If you're unsure, start simple and adopt patterns as needed:

  1. Start with UI — Use src/shared/ui/primitives/ for consistent styling
  2. Add behavioral hooks — When you notice repeated interaction logic, use src/shared/hooks/
  3. Adopt module structure — When features grow, organize into src/modules/<feature>/
  4. Enable DI — When you need testability or swappable implementations

There's no penalty for keeping things simple. The architecture exists to help at scale, not to impose overhead on small projects.


Design goals

Katalyst exists to make common admin UI behavior predictable and reusable.

It intentionally solves:

  • A consistent way to implement recurring interactions (selection, confirmation, inline edit, shortcuts, command palette)
  • A shared UI foundation so screens look and behave similarly
  • A structure where features can be added without rewriting app-level plumbing

It does not try to solve:

  • Your product’s domain model (entities, permissions, business rules)
  • Backend architecture, deployment, or hosting
  • Every UI variant via configuration or theme systems

The goal is to keep the “rules of interaction” shared, while keeping product behavior local to features.

Primitives-first approach

Shared primitives are small, focused building blocks (primarily hooks) that encapsulate behavior you want to be consistent across the app.

This approach exists because:

  • Centralizing behavior makes edge cases consistent (and easier to fix once)
  • Shared primitives prevent “almost the same” implementations from drifting over time
  • A stable primitive API makes patterns copy-safe across features

Duplication is avoided by default. If two screens solve the same interaction differently, you’ve created two maintenance paths.

Layers of the system

Katalyst is structured as a simple set of layers:

  • Shared primitives
    • Live in src/shared/hooks/.
    • Own interaction state and control flow.
  • Thin components
    • Live in src/shared/ui/components/.
    • Render UI around primitives (for example, a dialog that renders state from a hook).
    • Built on a shared primitive UI foundation in src/shared/ui/shadcn/.
  • Feature modules
    • Live in src/modules/.
    • Own product-specific pages, flows, and domain behavior.
    • Consume shared primitives/components rather than re-implementing them.
  • Showcase (consumer)
    • Lives in src/modules/showcase/.
    • Acts as a reference implementation and a “dogfooding” area for primitives and patterns.

Composition over configuration

Katalyst favors composition:

  • Primitives are meant to be combined in code to produce the behavior you need.
  • Patterns emerge from composing a small set of primitives, not from adding more configuration surface.

This is intentional. Configuration-heavy systems often hide behavior behind options and special cases. Composition keeps behavior explicit at the call site and easier to review.

Consistency and scalability

Consistency is enforced by how you build:

  • Reuse shared primitives for behavior that must match across screens
  • Keep UI components thin so they don’t become “mini frameworks”
  • Treat patterns as reusable compositions, not new layers

When adding new features:

  • Start inside a feature module (src/modules/...) and compose existing primitives.
  • Prefer small wrappers local to the feature before creating shared abstractions.

When to introduce a new primitive:

  • The behavior repeats across multiple features
  • The behavior has meaningful edge cases you want solved once
  • You can describe the API without referencing a specific domain entity

When not to introduce a new primitive:

  • The behavior is specific to one feature or one domain concept
  • The API would be mostly configuration and special cases
  • You can’t yet name the invariant you’re trying to standardize

What to do when extending Katalyst

Use this checklist to stay aligned:

  • Start with intent
    • Identify what must be consistent (interaction behavior) vs what is product-specific (domain rules).
  • Search for existing building blocks
    • Prefer reusing a shared primitive over re-implementing the same interaction.
  • Keep logic and UI separate
    • Put reusable behavior in primitives.
    • Keep components as render layers over that behavior.
  • Prove reuse before sharing
    • If you think something should be shared, validate that it applies to more than one feature.
  • Use Showcase as the reference consumer
    • If a primitive/pattern is meant to be reused, it should be demonstrable in src/modules/showcase/.

This keeps features consistent without forcing everything into a single global abstraction.