First-launch splash — centered brand mark and hero headline over stacked social sign-in pills, an email CTA, a terms line, and a sign-in link
import * as React from "react";
import { View, Pressable } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Button, Divider, Glass, Icon, Text } from "@lumioui/ui";
import { useHaptic, useTheme } from "@lumioui/core";
import { AppleLogo, GoogleLogo, Mount, SERIF, World } from "../shared";
export interface WelcomeScreenProps {
onSocialAuth?: (provider: "apple" | "google") => void;
onEmailSignUp?: () => void;
onSignIn?: () => void;
onTerms?: () => void;
onPrivacy?: () => void;
/** Overrides the default zap brand mark. */
logo?: React.ReactNode;
}
/** Social provider button — bordered pill with the provider's real
* brand mark, same idiom the login screen uses. */
function SocialButton({
label,
icon,
onPress,
}: {
label: string;
icon: React.ReactNode;
onPress?: () => void;
}) {
const haptics = useHaptic();
return (
<Button
variant="glass"
size="lg"
className="w-full"
icon={icon}
onPress={() => {
haptics.light();
onPress?.();
}}
accessibilityLabel={`Continue with ${label}`}
>
{label}
</Button>
);
}
SocialButton.displayName = "SocialButton";
/** Welcome — first-launch splash: centered brand hero over a stacked auth
* action column (SSO pills, email CTA, terms line, sign-in link). */
export function WelcomeScreen({
onSocialAuth,
onEmailSignUp,
onSignIn,
onTerms,
onPrivacy,
logo,
}: WelcomeScreenProps) {
const haptics = useHaptic();
const insets = useSafeAreaInsets();
const { colors } = useTheme();
return (
<World primary="#65A30D">
<SafeAreaView className="flex-1 bg-[#F4F5F9] dark:bg-[#111114]" edges={["top"]}>
<View
className="flex-1 justify-between"
style={{ paddingBottom: insets.bottom + 16 }}
>
{/* Color field — oversized bloom discs bleeding off the canvas
corner, the ambient gradient flagship welcome screens use. */}
<View
className="absolute -right-24 -top-20 h-80 w-80 rounded-full bg-[#65A30D]/15 dark:bg-[#65A30D]/25"
accessibilityElementsHidden
importantForAccessibility="no"
/>
<View
className="absolute -right-8 top-40 h-56 w-56 rounded-full bg-[#BEF04C]/15 dark:bg-[#BEF04C]/20"
accessibilityElementsHidden
importantForAccessibility="no"
/>
<View
className="absolute -left-16 top-8 h-40 w-40 rounded-full bg-[#65A30D]/10 dark:bg-[#65A30D]/15"
accessibilityElementsHidden
importantForAccessibility="no"
/>
{/* Action-zone bloom — the glass SSO pills need color beneath them
to refract, so the brand hue pools under the buttons too. */}
{/* Editorial hero — left-aligned display type owns the top two
thirds, brand mark is a small seal above it. No centered icon
stack. */}
<View className="flex-1 justify-end px-6 pb-10">
<Mount delay={0} className="gap-8">
<View>
{logo ?? (
<Glass interactive
effect="regular"
tintColor="#65A30DCC"
sheen={0.6}
className="h-12 w-12 items-center justify-center rounded-2xl"
>
<Icon name="zap" size="md" color="primaryForeground" />
</Glass>
)}
</View>
<View className="gap-4">
<Text
accessibilityRole="header"
className="text-[46px] font-bold leading-[50px] tracking-tight text-foreground"
style={{ fontFamily: SERIF }}
>
Build apps{"\n"}that feel{"\n"}
<Text className="italic text-[#65A30D]">alive.</Text>
</Text>
<Text variant="muted" className="max-w-[17rem] text-base leading-relaxed">
Every component, screen, and gesture — copy-paste React Native
source with motion baked in.
</Text>
{/* Social proof — a floating glass lozenge over the blooms. */}
<View className="mt-1 flex-row items-center gap-1 self-start overflow-hidden rounded-full px-3 py-1.5">
<Glass interactive effect="clear" sheen={0.5} className="absolute inset-0 rounded-full" pointerEvents="none" />
{[0, 1, 2, 3, 4].map((s) => (
<Icon key={s} name="star" size="sm" color="#EAB308" />
))}
<Text variant="small" className="pl-1 text-xs font-medium text-muted-foreground">
4.9 on the App Store
</Text>
</View>
</View>
</Mount>
</View>
{/* Actions — SSO pills, email CTA, legal, sign-in. */}
<View className="gap-4 px-6">
<Mount delay={140}>
<View className="gap-3">
<SocialButton
label="Apple"
icon={<AppleLogo color={colors.foreground} />}
onPress={() => onSocialAuth?.("apple")}
/>
<SocialButton
label="Google"
icon={<GoogleLogo />}
onPress={() => onSocialAuth?.("google")}
/>
</View>
</Mount>
<Mount delay={220}>
<Divider
label="or"
labelClassName="text-xs uppercase tracking-widest"
className="px-2"
/>
</Mount>
<Mount delay={300}>
<Button
size="lg"
haptic="medium"
className="bg-[#65A30D] w-full"
icon={<Icon name="mail" size="sm" color="primaryForeground" />}
onPress={onEmailSignUp}
accessibilityLabel="Continue with email"
>
Continue with email
</Button>
</Mount>
{/* Terms — nested Text onPress keeps the two links inline with the
wrapped sentence. */}
<Mount delay={380}>
<Text variant="muted" className="px-4 text-center text-xs leading-5">
By continuing, you agree to our{" "}
<Text
className="font-medium text-foreground"
accessibilityRole="link"
onPress={() => {
haptics.light();
onTerms?.();
}}
>
Terms of Service
</Text>{" "}
and{" "}
<Text
className="font-medium text-foreground"
accessibilityRole="link"
onPress={() => {
haptics.light();
onPrivacy?.();
}}
>
Privacy Policy
</Text>
.
</Text>
</Mount>
<Mount delay={460}>
<Pressable
onPress={() => {
haptics.light();
onSignIn?.();
}}
className="min-h-11 items-center justify-center px-2"
accessibilityRole="link"
accessibilityLabel="Sign in to an existing account"
hitSlop={8}
>
<Text variant="muted">
Already have an account?{" "}
<Text className="font-semibold text-[#65A30D]">Sign in</Text>
</Text>
</Pressable>
</Mount>
</View>
</View>
</SafeAreaView>
</World>
);
}
WelcomeScreen.displayName = "WelcomeScreen";
Recreate the "Welcome Screen" screen in React Native using LumioUI.
Goal: First-launch splash — centered brand mark and hero headline over stacked social sign-in pills, an email CTA, a terms line, and a sign-in link Match the reference implementation's layout, behavior, and motion exactly — this is a production screen, not a mockup.
## Setup
Fastest path — install the screen plus its component deps:
```bash
npx lumioui add auth-welcome
```
Or pull just the components: `npx lumioui add button divider icon text`
Native packages:
- @lumioui/ui@latest
- @lumioui/core@latest
- react-native-safe-area-context@*
- react-native-reanimated@*
- react-native-svg@*
- nativewind@*
Stack assumptions: Expo SDK 54 / RN 0.81, Reanimated 4, NativeWind v4, TypeScript strict, Expo Go compatible.
## Components — how, where, when
### `button` — Tactile pill button with press spring, ink wash and async loading.
- `<Button>` ×2
### `divider` — Hairline rule with optional centered label, horizontal or vertical.
- `<Divider>` ×1
### `icon` — Lucide icon set mapped to theme colors and sizes.
- `<Icon>` ×3
### `text` — Themed type scale from caption to display.
- `<Text>` ×9
## Conventions (non-negotiable)
- Styling via NativeWind `className` with semantic tokens only: `bg-background`, `text-foreground`, `bg-card`, `text-card-foreground`, `bg-secondary`, `bg-muted`, `text-muted-foreground`, `bg-accent`, `bg-destructive`, `border-border`. Hard-coded colors only inside media overlays (`text-white`, `bg-black/N`).
- Motion via the token presets from `@lumioui/core` (`spring.press`, `spring.surface`, `spring.glide`, `timing.fast`, `timing.slow`, …) — no bespoke spring configs or durations.
- Gate transform motion behind `useReducedMotion()`; keep opacity/color feedback, drop travel and overshoot.
- Haptics via `useHaptic()`: `light` on press, `selection` on toggle/select, `warning` on invalid input. Never import expo-haptics directly.
- Press feedback via `usePressAnimation()` (~0.97 scale), not `activeOpacity` tricks.
- a11y: `accessibilityRole` + `accessibilityLabel` on every control, touch targets ≥44pt.
- Entrance choreography: staggered `Mount` wrappers (delay 0/60/120…) so regions land in sequence instead of popping in at once.
- Wrap the screen in `SafeAreaView`; keep dark mode working via the semantic tokens.
## Deliverable
One file: `screens/auth/welcome.tsx` exporting WelcomeScreen. Preserve the props interface — the host app wires the callbacks (navigation, submit handlers) through them.
## Reference implementation
The LumioUI source for this screen — reproduce it faithfully:
```tsx
import * as React from "react";
import { View, Pressable } from "react-native";
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context";
import { Button, Divider, Glass, Icon, Text } from "@lumioui/ui";
import { useHaptic, useTheme } from "@lumioui/core";
import { AppleLogo, GoogleLogo, Mount, SERIF, World } from "../shared";
export interface WelcomeScreenProps {
onSocialAuth?: (provider: "apple" | "google") => void;
onEmailSignUp?: () => void;
onSignIn?: () => void;
onTerms?: () => void;
onPrivacy?: () => void;
/** Overrides the default zap brand mark. */
logo?: React.ReactNode;
}
/** Social provider button — bordered pill with the provider's real
* brand mark, same idiom the login screen uses. */
function SocialButton({
label,
icon,
onPress,
}: {
label: string;
icon: React.ReactNode;
onPress?: () => void;
}) {
const haptics = useHaptic();
return (
<Button
variant="glass"
size="lg"
className="w-full"
icon={icon}
onPress={() => {
haptics.light();
onPress?.();
}}
accessibilityLabel={`Continue with ${label}`}
>
{label}
</Button>
);
}
SocialButton.displayName = "SocialButton";
/** Welcome — first-launch splash: centered brand hero over a stacked auth
* action column (SSO pills, email CTA, terms line, sign-in link). */
export function WelcomeScreen({
onSocialAuth,
onEmailSignUp,
onSignIn,
onTerms,
onPrivacy,
logo,
}: WelcomeScreenProps) {
const haptics = useHaptic();
const insets = useSafeAreaInsets();
const { colors } = useTheme();
return (
<World primary="#65A30D">
<SafeAreaView className="flex-1 bg-[#F4F5F9] dark:bg-[#111114]" edges={["top"]}>
<View
className="flex-1 justify-between"
style={{ paddingBottom: insets.bottom + 16 }}
>
{/* Color field — oversized bloom discs bleeding off the canvas
corner, the ambient gradient flagship welcome screens use. */}
<View
className="absolute -right-24 -top-20 h-80 w-80 rounded-full bg-[#65A30D]/15 dark:bg-[#65A30D]/25"
accessibilityElementsHidden
importantForAccessibility="no"
/>
<View
className="absolute -right-8 top-40 h-56 w-56 rounded-full bg-[#BEF04C]/15 dark:bg-[#BEF04C]/20"
accessibilityElementsHidden
importantForAccessibility="no"
/>
<View
className="absolute -left-16 top-8 h-40 w-40 rounded-full bg-[#65A30D]/10 dark:bg-[#65A30D]/15"
accessibilityElementsHidden
importantForAccessibility="no"
/>
{/* Action-zone bloom — the glass SSO pills need color beneath them
to refract, so the brand hue pools under the buttons too. */}
{/* Editorial hero — left-aligned display type owns the top two
thirds, brand mark is a small seal above it. No centered icon
stack. */}
<View className="flex-1 justify-end px-6 pb-10">
<Mount delay={0} className="gap-8">
<View>
{logo ?? (
<Glass interactive
effect="regular"
tintColor="#65A30DCC"
sheen={0.6}
className="h-12 w-12 items-center justify-center rounded-2xl"
>
<Icon name="zap" size="md" color="primaryForeground" />
</Glass>
)}
</View>
<View className="gap-4">
<Text
accessibilityRole="header"
className="text-[46px] font-bold leading-[50px] tracking-tight text-foreground"
style={{ fontFamily: SERIF }}
>
Build apps{"\n"}that feel{"\n"}
<Text className="italic text-[#65A30D]">alive.</Text>
</Text>
<Text variant="muted" className="max-w-[17rem] text-base leading-relaxed">
Every component, screen, and gesture — copy-paste React Native
source with motion baked in.
</Text>
{/* Social proof — a floating glass lozenge over the blooms. */}
<View className="mt-1 flex-row items-center gap-1 self-start overflow-hidden rounded-full px-3 py-1.5">
<Glass interactive effect="clear" sheen={0.5} className="absolute inset-0 rounded-full" pointerEvents="none" />
{[0, 1, 2, 3, 4].map((s) => (
<Icon key={s} name="star" size="sm" color="#EAB308" />
))}
<Text variant="small" className="pl-1 text-xs font-medium text-muted-foreground">
4.9 on the App Store
</Text>
</View>
</View>
</Mount>
</View>
{/* Actions — SSO pills, email CTA, legal, sign-in. */}
<View className="gap-4 px-6">
<Mount delay={140}>
<View className="gap-3">
<SocialButton
label="Apple"
icon={<AppleLogo color={colors.foreground} />}
onPress={() => onSocialAuth?.("apple")}
/>
<SocialButton
label="Google"
icon={<GoogleLogo />}
onPress={() => onSocialAuth?.("google")}
/>
</View>
</Mount>
<Mount delay={220}>
<Divider
label="or"
labelClassName="text-xs uppercase tracking-widest"
className="px-2"
/>
</Mount>
<Mount delay={300}>
<Button
size="lg"
haptic="medium"
className="bg-[#65A30D] w-full"
icon={<Icon name="mail" size="sm" color="primaryForeground" />}
onPress={onEmailSignUp}
accessibilityLabel="Continue with email"
>
Continue with email
</Button>
</Mount>
{/* Terms — nested Text onPress keeps the two links inline with the
wrapped sentence. */}
<Mount delay={380}>
<Text variant="muted" className="px-4 text-center text-xs leading-5">
By continuing, you agree to our{" "}
<Text
className="font-medium text-foreground"
accessibilityRole="link"
onPress={() => {
haptics.light();
onTerms?.();
}}
>
Terms of Service
</Text>{" "}
and{" "}
<Text
className="font-medium text-foreground"
accessibilityRole="link"
onPress={() => {
haptics.light();
onPrivacy?.();
}}
>
Privacy Policy
</Text>
.
</Text>
</Mount>
<Mount delay={460}>
<Pressable
onPress={() => {
haptics.light();
onSignIn?.();
}}
className="min-h-11 items-center justify-center px-2"
accessibilityRole="link"
accessibilityLabel="Sign in to an existing account"
hitSlop={8}
>
<Text variant="muted">
Already have an account?{" "}
<Text className="font-semibold text-[#65A30D]">Sign in</Text>
</Text>
</Pressable>
</Mount>
</View>
</View>
</SafeAreaView>
</World>
);
}
WelcomeScreen.displayName = "WelcomeScreen";
```
Screen: https://lumioui.dev/screens/auth-welcome