mdcms/sample-sites/velox-docs/pages/components.md
2026-05-18 14:30:49 +07:00

5.9 KiB

title sort section-id keywords description language
Components 110 core-concepts components, props, slots, lifecycle, tsx, velox components, islands The Velox component model, including props, slots, lifecycle hooks, and the islands architecture en

Components

Velox components are TypeScript/TSX files that describe a piece of UI. They are the fundamental building blocks of a Velox application — reusable, composable, and by default rendered entirely on the server.

Defining a Component

A basic Velox component is a TypeScript function that returns JSX:

// components/Greeting.tsx
interface GreetingProps {
  name: string;
  formal?: boolean;
}

export default function Greeting({ name, formal = false }: GreetingProps) {
  const salutation = formal ? 'Good day' : 'Hello';
  return <p>{salutation}, {name}!</p>;
}

Use it in a route or another component:

---
import Greeting from '../components/Greeting.tsx';
---

<main>
  <Greeting name="Alice" />
  <Greeting name="Bob" formal />
</main>

Props

Props are typed using TypeScript interfaces or types. All props are validated at compile time — no runtime prop checking overhead.

interface CardProps {
  title: string;
  description: string;
  href?: string;
  variant?: 'default' | 'featured' | 'compact';
  children?: VeloxNode;
}

export default function Card({
  title,
  description,
  href,
  variant = 'default',
  children,
}: CardProps) {
  return (
    <div class={`card card--${variant}`}>
      <h3>{href ? <a href={href}>{title}</a> : title}</h3>
      <p>{description}</p>
      {children && <div class="card__body">{children}</div>}
    </div>
  );
}

Default Props

Set default values directly in the destructuring parameter, as shown above. Velox does not use a separate defaultProps mechanism.

Required vs Optional Props

By TypeScript convention, optional props are marked with ?. Omitting a required prop is a compile-time error.

Slots

The children prop is the default slot — content placed between the opening and closing tags of a component:

<Card title="Welcome" description="Get started">
  <p>This is the card body.</p>
</Card>

Named Slots

For more complex component APIs, use named slot props:

interface ModalProps {
  title: VeloxNode;
  footer?: VeloxNode;
  children: VeloxNode;
}

export default function Modal({ title, footer, children }: ModalProps) {
  return (
    <div class="modal">
      <div class="modal__header">{title}</div>
      <div class="modal__body">{children}</div>
      {footer && <div class="modal__footer">{footer}</div>}
    </div>
  );
}

Usage:

<Modal
  title={<h2>Confirm Delete</h2>}
  footer={
    <>
      <button onClick={cancel}>Cancel</button>
      <button onClick={confirm}>Delete</button>
    </>
  }
>
  <p>Are you sure you want to delete this item?</p>
</Modal>

Server vs Client Components

By default, all Velox components run on the server. They have no JavaScript bundle size on the client and cannot use browser APIs or client-side reactivity.

To make a component interactive on the client, use the client:* hydration directive when you include it in a route:

---
import Counter from '../components/Counter.tsx';
import LazyChart from '../components/LazyChart.tsx';
---

<!-- Hydrate immediately when page loads -->
<Counter client:load />

<!-- Hydrate when browser is idle -->
<Counter client:idle />

<!-- Hydrate when the component enters the viewport -->
<LazyChart client:visible />

<!-- Hydrate only when a media query matches -->
<Sidebar client:media="(max-width: 768px)" />

Writing Client Components

Client components can use signals, effects, and browser APIs:

import { signal, effect, onMount, onCleanup } from 'velox/client';

export default function LiveClock() {
  const time = signal(new Date().toLocaleTimeString());

  onMount(() => {
    const interval = setInterval(() => {
      time.value = new Date().toLocaleTimeString();
    }, 1000);

    onCleanup(() => clearInterval(interval));
  });

  return <p>Current time: {time}</p>;
}

Lifecycle Hooks

Client-side components can use the following lifecycle hooks:

Hook When it runs
onMount(fn) After the component is first rendered and inserted into the DOM
onUpdate(fn) After every re-render (reactive signal change)
onCleanup(fn) Before the component unmounts or before the next onUpdate call
onDestroy(fn) When the component is permanently unmounted
import { signal, onMount, onUpdate, onCleanup } from 'velox/client';

export default function DataComponent() {
  const data = signal<any[]>([]);
  const loading = signal(true);

  onMount(async () => {
    const response = await fetch('/api/data');
    data.value = await response.json();
    loading.value = false;
  });

  onCleanup(() => {
    // abort pending requests, clear timers, etc.
  });

  return (
    <div>
      {loading.value ? <p>Loading...</p> : <ul>{data.value.map(d => <li>{d.name}</li>)}</ul>}
    </div>
  );
}

Component Composition Patterns

Higher-Order Components

function withAuth<T extends {}>(Component: VeloxComponent<T>) {
  return function AuthGated(props: T) {
    const user = useUser();
    if (!user) return <Redirect to="/login" />;
    return <Component {...props} />;
  };
}

Render Props / Function-as-Children

interface FetcherProps<T> {
  url: string;
  render: (data: T) => VeloxNode;
}

export function Fetcher<T>({ url, render }: FetcherProps<T>) {
  // ... fetch logic
  return render(data);
}

Component Library

Velox ships an optional official component library @velox/ui with accessible, unstyled base components. Install it separately:

npm install @velox/ui
import { Button, Input, Dialog } from '@velox/ui';

See the Component API reference for the full defineComponent API and advanced component patterns.