Components / Outcome Patterns
Outcome Complete
Achievement confirmation with auto-return, next-step guidance, and outcome summary.
Proposal sent
Achievement with next-step actions and auto-return countdown.
Proposal sent to 3 recipients
Smith Family Safari — Booking Quote v2
Or continue with
Payment collected
Financial outcome with download and summary options.
Payment of $2,450.00 collected
Booking #4821 — Smith Family Safari
Or continue with
Preferences saved
Minimal success — no next steps, just a return action.
Preferences saved
Smith Family Safari — 3 traveller profiles updated
Minimal (no return context)
When no returnLabel is available, the action reads "Done".
Document published
Available at the viewer link
Source OutcomeCompleteDemo.tsx
The exact code behind the live demo above. Fetch it raw at /components/source/outcome-complete.txt.
import React, { useState, useEffect, useRef } from 'react';
function Section({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) {
return (
<div className="mb-8">
<h3 className="text-sm font-bold text-[var(--flux-heading)] mb-1">{title}</h3>
{description && (
<p className="text-xs text-[var(--flux-grey-500)] mb-3">{description}</p>
)}
<div className="rounded border border-[var(--flux-grey-100)] bg-[var(--flux-surface)] overflow-hidden">
{children}
</div>
</div>
);
}
function CheckCircle() {
return (
<div className="w-14 h-14 rounded-full bg-[color:var(--flux-green-100)] flex items-center justify-center">
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" stroke="var(--flux-green-400)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<polyline points="20 8 11 18 7 14" />
</svg>
</div>
);
}
interface OutcomeCompleteProps {
achievement: string;
detail?: string;
returnLabel?: string;
nextSteps?: { label: string; accent?: string }[];
autoReturn?: boolean;
countdownMs?: number;
}
function OutcomeComplete({
achievement,
detail,
returnLabel,
nextSteps,
autoReturn = false,
countdownMs = 5000,
}: OutcomeCompleteProps) {
const [countdown, setCountdown] = useState(Math.ceil(countdownMs / 1000));
const [cancelled, setCancelled] = useState(false);
const intervalRef = useRef<ReturnType<typeof setInterval>>();
useEffect(() => {
if (!autoReturn || cancelled) return;
intervalRef.current = setInterval(() => {
setCountdown((c) => {
if (c <= 1) {
clearInterval(intervalRef.current);
return 0;
}
return c - 1;
});
}, 1000);
return () => clearInterval(intervalRef.current);
}, [autoReturn, cancelled]);
const handleCancel = () => {
setCancelled(true);
clearInterval(intervalRef.current);
};
const returnText = returnLabel ? `Back to ${returnLabel}` : 'Done';
return (
<div className="flex flex-col items-center justify-center py-12 px-6 text-center gap-4">
<CheckCircle />
<div>
<p className="text-lg font-bold text-[var(--flux-heading)] mb-1">{achievement}</p>
{detail && (
<p className="text-sm text-[var(--flux-grey-500)]">{detail}</p>
)}
</div>
<div className="flex flex-col items-center gap-3 mt-2">
<button className="px-5 py-2.5 text-sm font-bold rounded bg-[var(--flux-primary-400)] text-white hover:opacity-90 transition-opacity">
{autoReturn && !cancelled && countdown > 0
? `${returnText} (${countdown}s)`
: returnText}
</button>
{autoReturn && !cancelled && countdown > 0 && (
<button
onClick={handleCancel}
className="text-xs text-[var(--flux-grey-300)] hover:text-[var(--flux-grey-500)] transition-colors"
>
Cancel auto-return
</button>
)}
</div>
{nextSteps && nextSteps.length > 0 && (
<div className="mt-4 pt-4 border-t border-[var(--flux-grey-100)] w-full max-w-sm">
<p className="text-xs text-[var(--flux-grey-300)] mb-3">Or continue with</p>
<div className="flex flex-wrap justify-center gap-2">
{nextSteps.map((step) => (
<button
key={step.label}
className="px-3 py-1.5 text-xs font-bold rounded border border-[var(--flux-grey-100)] text-[var(--flux-grey-500)] hover:border-[var(--flux-primary-300)] hover:text-[var(--flux-primary-400)] transition-colors"
>
{step.label}
</button>
))}
</div>
</div>
)}
</div>
);
}
export default function OutcomeCompleteDemo() {
return (
<div>
<Section
title="Proposal sent"
description="Achievement with next-step actions and auto-return countdown."
>
<OutcomeComplete
achievement="Proposal sent to 3 recipients"
detail="Smith Family Safari — Booking Quote v2"
returnLabel="Booking"
autoReturn
nextSteps={[
{ label: 'Collect Payment' },
{ label: 'View Proposal' },
]}
/>
</Section>
<Section
title="Payment collected"
description="Financial outcome with download and summary options."
>
<OutcomeComplete
achievement="Payment of $2,450.00 collected"
detail="Booking #4821 — Smith Family Safari"
returnLabel="Itinerary"
nextSteps={[
{ label: 'Download Receipt' },
{ label: 'View Financial Summary' },
]}
/>
</Section>
<Section
title="Preferences saved"
description="Minimal success — no next steps, just a return action."
>
<OutcomeComplete
achievement="Preferences saved"
detail="Smith Family Safari — 3 traveller profiles updated"
returnLabel="Booking"
/>
</Section>
<Section
title="Minimal (no return context)"
description='When no returnLabel is available, the action reads "Done".'
>
<OutcomeComplete
achievement="Document published"
detail="Available at the viewer link"
/>
</Section>
</div>
);
}