import { Box, Button, VStack } from '@/components/ui/box';
export default function FocusExample() {
return (
<VStack className="gap-4 p-4">
<Text className="text-typography-500">
Notice the high-contrast ring when tabbing through these actions:
</Text>
<HStack className="gap-2">
<Button
className="focus:ring-4 focus:ring-primary-500 outline-none"
action="outline"
>
Cancel
</Button>
<Button
className="focus:ring-4 focus:ring-primary-500 outline-none"
action="primary"
>
Submit Form
</Button>
</HStack>
</VStack>
);
}
import { Box, Button, Modal, VStack, Text } from '@/components/ui/box';
export default function ModalFocusTrap() {
return (
<Box className="p-4">
<Modal>
<Modal.Trigger>
<Button action="primary">Open Settings</Button>
</Modal.Trigger>
<Modal.Content className="p-6">
<VStack className="gap-4">
<Heading className="text-typography-900">Form Settings</Heading>
<Text className="text-typography-600">
Focus is now trapped here until you close the modal.
</Text>
<Button action="primary">Save Changes</Button>
</VStack>
</Modal.Content>
</Modal>
</Box>
);
}
import React, { useState } from 'react';
import { Box } from '@/components/ui/box';
import { VStack } from '@/components/ui/vstack';
import { Input } from '@/components/ui/input';
import { Text } from '@/components/ui/text';
import { Button } from '@/components/ui/button';
import { FormControl } from '@/components/ui/form-control';
import { FormControlErrorText } from '@/components/ui/form-control-error-text';
export default function AccessibleValidation() {
const [email, setEmail] = useState('');
const [error, setError] = useState(false);
const validate = () => {
const isValid = email.includes('@');
setError(!isValid);
};
return (
<Box className="p-4 w-full max-w-md">
<VStack className="gap-4">
<FormControl isInvalid={error}>
<Text className="text-typography-900 font-bold mb-2">
Email Address
</Text>
<Input
placeholder="enter your email"
value={email}
onChangeText={setEmail}
className={`${error ? 'border-outline-600' : 'border-outline-300'}`}
aria-invalid={error}
aria-describedby="email-error-text"
/>
{error && (
<FormControlErrorText
id="email-error-text"
className="text-error-600"
aria-live="polite"
>
Please enter a valid email address.
</FormControlErrorText>
)}
</FormControl>
<Button
className="bg-primary-500"
action={validate}
>
Submit
</Button>
</VStack>
</Box>
);
}
import React, { useState, useRef } from 'react';
import { Box } from '@/components/ui/box';
import { VStack } from '@/components/ui/vstack';
import { Text } from '@/components/ui/text';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
export default function MultiStepForm() {
const [step, setStep] = useState(1);
const headingRef = useRef<HTMLHeadingElement>(null);
const nextStep = () => {
setStep((prev) => prev + 1);
// Focus management: move focus to the new section heading
setTimeout(() => headingRef.current?.focus(), 100);
};
return (
<Box className="p-4 bg-background-50">
{/* Accessible Progress Indicator */}
<VStack className="gap-2 mb-6">
<Text className="text-typography-500 text-sm">
Step {step} of 2
</Text>
<Box className="h-2 w-full bg-background-200 rounded-full overflow-hidden">
<Box
className="h-full bg-primary-500 transition-all duration-300"
style={{ width: `${(step / 2) * 100}%` }}
/>
</Box>
<Text className="sr-only" aria-live="polite">
Currently on step {step} of 2
</Text>
</VStack>
{step === 1 && (
<VStack className="gap-4">
<Text
ref={headingRef}
tabIndex={-1}
className="text-typography-900 text-xl font-bold outline-none"
>
Personal Information
</Text>
<fieldset className="border-0 p-0 m-0">
<legend className="sr-only">Contact Details</legend>
<VStack className="gap-3">
<Box>
<Text className="text-typography-700 mb-1">Full Name</Text>
<Input className="border-outline-300" placeholder="John Doe" />
</Box>
<Box>
<Text className="text-typography-700 mb-1">Email Address</Text>
<Input className="border-outline-300" placeholder="john@example.com" />
</Box>
</VStack>
</fieldset>
<Button
className="bg-primary-600"
action={nextStep}
variant="solid"
>
Continue
</Button>
</VStack>
)}
{step === 2 && (
<VStack className="gap-4">
<Text
ref={headingRef}
tabIndex={-1}
className="text-typography-900 text-xl font-bold outline-none"
>
Shipping Address
</Text>
<fieldset className="border border-outline-200 p-4 rounded-md">
<legend className="px-2 text-typography-600 text-sm font-medium">
Delivery Location
</legend>
<VStack className="gap-3 mt-2">
<Box>
<Text className="text-typography-700 mb-1">Street Address</Text>
<Input className="border-outline-300" />
</Box>
<Box>
<Text className="text-typography-700 mb-1">City</Text>
<Input className="border-outline-300" />
</Box>
</VStack>
</fieldset>
<HStack className="gap-3">
<Button
className="flex-1 border-outline-300"
action={() => setStep(1)}
variant="outline"
>
Back
</Button>
<Button
className="flex-1 bg-primary-600"
action={() => alert('Submitted!')}
variant="solid"
>
Submit
</Button>
</HStack>
</VStack>
)}
</Box>
);
}
import { Box, VStack, Text, Input } from '@/components/ui';
export default function AccessibleInput() {
return (
<Box className="p-4 w-full max-w-sm">
<VStack className="gap-2">
<Text className="text-typography-900 font-bold">Email Address</Text>
<Input
className="border-outline-500 focus:border-primary-500"
placeholder="email@example.com"
aria-invalid="true"
aria-describedby="email-error"
/>
<Text
id="email-error"
className="text-error-600 text-sm"
>
Please enter a valid email address.
</Text>
</VStack>
</Box>
);
}