Documentation Index
Fetch the complete documentation index at: https://docs.fanfare.io/llms.txt
Use this file to discover all available pages before exploring further.
Component Customization
Fanfare widgets support customization through slots and render props, allowing you to replace specific parts while keeping the widget functionality.
Customization Approaches
| Approach | Customization Level | Complexity |
|---|
| CSS Variables | Styling only | Low |
| Slots | Partial replacement | Medium |
| Render Props | Full control | High |
| Hooks | Complete freedom | Highest |
Slots (SolidJS/Web Components)
Widgets built with SolidJS support slot-based customization:
// Using Solid SDK directly
import { QueueWidget } from "@waitify-io/fanfare-sdk-solid";
function CustomQueueWidget() {
return (
<QueueWidget
queueId="queue_123"
slots={{
header: (props) => (
<div className="custom-header">
<img src="/logo.svg" alt="Brand" />
<h2>{props.title}</h2>
<p>Position: {props.position}</p>
</div>
),
position: (props) => (
<div className="custom-position">
<span className="number">{props.position}</span>
<span className="label">in line</span>
</div>
),
actions: (props) => (
<div className="custom-actions">
{props.status === "enterable" && (
<button onClick={props.onEnter} disabled={props.isEntering}>
{props.isEntering ? "Joining..." : "Join Now"}
</button>
)}
{props.status === "queued" && (
<button onClick={props.onLeave} disabled={props.isLeaving}>
Leave Queue
</button>
)}
</div>
),
}}
/>
);
}
| Slot | Props |
|---|
header | status, position, estimatedWaitMinutes, title, description |
position | status, position, estimatedWaitMinutes |
actions | status, onEnter, onLeave, isEntering, isLeaving |
| Slot | Props |
|---|
header | status, drawTime, timeRemaining, title, description |
countdown | status, drawTime, timeRemaining |
actions | status, onEnter, onWithdraw, onProceed, isEntering |
| Slot | Props |
|---|
header | status, currentBid, myBid, isWinning, title, description |
bidDisplay | status, currentBid, myBid, minNextBid, reservePrice, reserveMet |
bidForm | status, minNextBid, bidIncrement, onBid, isBidding |
bidHistory | status, bidHistory |
countdown | status, endTime, timeRemaining |
| Slot | Props |
|---|
start | snapshot, status, onStart, isStarting |
loading | snapshot, status, message |
auth | snapshot, status, onSubmit, onVerify, onSkip |
accessCode | snapshot, status, onSubmit, onSkip |
upcoming | snapshot, status, startsAt, canEnterWaitlist, onEnterWaitlist |
waitlist | snapshot, status, position, startsAt, onLeaveWaitlist |
enterable | snapshot, status, participationType, onEnter |
participating | snapshot, status, participationType, position, onLeave |
admitted | snapshot, status, admittanceToken, expiresAt |
expired | snapshot, status, onReenter, isReentering |
ended | snapshot, status, endedAt |
error | snapshot, status, error, onRetry |
Render Props
For complete control, use the render prop pattern:
import { QueueWidget } from "@waitify-io/fanfare-sdk-solid";
function FullyCustomQueue() {
return (
<QueueWidget queueId="queue_123">
{({ status, position, estimatedWaitMinutes, enter, leave, isEntering, isLeaving, isLoading, error }) => (
<div className="my-queue-design">
{isLoading ? (
<MyLoadingSpinner />
) : error ? (
<MyErrorDisplay error={error} />
) : status === "admitted" ? (
<MySuccessView />
) : status === "queued" ? (
<MyQueuedView
position={position}
estimatedWait={estimatedWaitMinutes}
onLeave={leave}
isLeaving={isLeaving}
/>
) : (
<MyEnterView onEnter={enter} isEntering={isEntering} />
)}
</div>
)}
</QueueWidget>
);
}
React Hook Alternative
For maximum flexibility with React, use hooks instead of widgets:
import { useQueue } from "@waitify-io/fanfare-sdk-react";
function CompletelyCustomQueue() {
const { queue, status, position, estimatedWait, enter, leave, isLoading, error } = useQueue("queue_123");
// Build your own UI with full design freedom
return (
<div className="my-brand-queue">
<MyBrandHeader />
{status === "queued" && <MyBrandQueuePosition position={position} estimatedWait={estimatedWait} />}
<MyBrandActions status={status} onEnter={enter} onLeave={leave} isLoading={isLoading} />
{error && <MyBrandError error={error} />}
</div>
);
}
Composing Custom Components
interface CustomHeaderProps {
status: string;
title: string;
subtitle?: string;
icon?: React.ReactNode;
}
function CustomHeader({ status, title, subtitle, icon }: CustomHeaderProps) {
return (
<div className="custom-header">
<div className="header-icon">{icon || <DefaultIcon status={status} />}</div>
<h2 className="header-title">{title}</h2>
{subtitle && <p className="header-subtitle">{subtitle}</p>}
</div>
);
}
// Use in slot
<QueueWidget
queueId="queue_123"
slots={{
header: (props) => (
<CustomHeader
status={props.status}
title="Join Our VIP Line"
subtitle={props.status === "queued" ? `You are #${props.position} in line` : "Get exclusive access"}
icon={<VIPIcon />}
/>
),
}}
/>;
interface CustomActionsProps {
status: string;
onEnter: () => Promise<void>;
onLeave: () => Promise<void>;
isEntering: boolean;
isLeaving: boolean;
}
function CustomActions({ status, onEnter, onLeave, isEntering, isLeaving }: CustomActionsProps) {
if (status === "admitted") {
return (
<a href="/checkout" className="checkout-button">
Continue to Checkout
</a>
);
}
if (status === "queued") {
return (
<button onClick={onLeave} disabled={isLeaving} className="leave-button">
{isLeaving ? "Leaving..." : "Leave Queue"}
</button>
);
}
return (
<button onClick={onEnter} disabled={isEntering} className="enter-button">
{isEntering ? (
<>
<Spinner /> Joining...
</>
) : (
"Join Queue"
)}
</button>
);
}
Custom Position Display
interface CustomPositionProps {
position: number;
estimatedWaitMinutes: number | null;
}
function CustomPosition({ position, estimatedWaitMinutes }: CustomPositionProps) {
return (
<div className="position-display">
<div className="position-number">
<span className="number">{position}</span>
<span className="label">in line</span>
</div>
{estimatedWaitMinutes && (
<div className="wait-estimate">
<ClockIcon />
<span>
{estimatedWaitMinutes < 60
? `~${estimatedWaitMinutes} min`
: `~${Math.round(estimatedWaitMinutes / 60)} hr`}
</span>
</div>
)}
<ProgressBar value={100 - position} max={100} className="position-progress" />
</div>
);
}
Mixing Approaches
Combine approaches for the right balance:
// Use widget for structure, slots for specific customization
<ExperienceWidget
experienceId="exp_123"
autoStart
slots={{
// Custom admitted view
admitted: (props) => <MyBrandSuccessView token={props.admittanceToken} expiresAt={props.expiresAt} />,
// Custom error handling
error: (props) => <MyBrandErrorView error={props.error} onRetry={props.onRetry} />,
// Keep default for other stages
}}
/>