Expokit
AboutDocsComponentsChartsBlocksPricing
Sign inGet started
Overview3D ButtonCard StackProCube PagerProFlip CardGift CardProLoyalty CardProParallax LayersProParallax ScrollProPayment CardRing CarouselProSwipe DeckProTicketProTilt CardWheel ListPro
Flip Card
Sponsor ExpoKit

Free stays free. Tip once — keep shipping.

Support
About
Docs
IntroductionWhat ExpoKit is and how it worksInstallationAdd ExpoKit to an Expo appThemingColours, radius and dark modeCLIinit, add, login and updateAI assistants (MCP)Cursor, Claude, Antigravity and moreRun on your phoneTry components in Expo GoChangelogWhat shipped recently
npx @expokit/cli@latest initQuick start
Components
Liquid Glass8 iOS 26 glass pieces3DNew14 tilt, flip and stack
Inputs 12
  • CalendarNew
  • Date PickerNew
  • Floating Input
  • OTP Input
  • Password Input
  • +7 more
Selection 7
  • Checkbox
  • Chip Group
  • Color SwatchPro
  • Radio Group
  • RatingPro
  • +2 more
Buttons 7
  • Button
  • Floating Action ButtonPro
  • Like ButtonPro
  • Morph ButtonPro
  • Shimmer ButtonPro
  • +2 more
Feedback 5
  • Alert
  • Progress
  • Skeleton
  • Spinner
  • Toast
Overlays 4
  • Action SheetNew
  • Bottom Sheet
  • Dialog
  • DrawerNew
Display 9
  • Accordion
  • Avatar
  • Badge
  • Card
  • CarouselPro
  • +4 more
Navigation 3
  • Bottom NavNew
  • Progress StepsNew
  • Tabs
Motion 2
  • Number TickerPro
  • Text Animation
70 components · 347 live variantsAll components
Charts

Cartesian

  • Area Chart
  • Bar Chart
  • Line Chart
  • Combo Chart

Circular

  • Pie & Donut
  • Radial Chart
  • Radar Chart
  • Polar AreaPro

3D & Pixel

  • Isometric BarsPro
  • Pixel BarsPro

Specialty

  • TreemapPro
  • WaterfallPro
  • BulletPro
  • TimelinePro
  • Gauge
  • Candlestick
  • Heatmap
  • Funnel

Classic

  • Sparkline
  • Progress Rings
  • MultiLine (classic)
  • StackedBar (classic)
  • Ranking Bars (classic)

Guides

  • Usage & config
  • Patterns & effects
  • Show / hide anything
  • Chart theming
  • Interactions
23 charts, built on the open-source expo-charts engineAll charts
Blocks

Screens

ChatProAn AI assistant chat screenCheckoutProA premium checkoutDashboardProA finance / analytics home screenInboxProA messaging inboxOnboardingProA minimal swipeable welcome flowProfileProAccount profile for any appSearch ResultsProA clean, monochrome search screenSettingsProAn iOS-style grouped settings screenSign InProOne configurable sign-in flow modelled on top mobile apps
9 complete screens — drop one in, wire your dataSee them on devices All blocks
Pricing
Docs3DFlip Card

Flip Card

Scan with Expo Go

Flip Card with all its variants, native in Expo Go. No account needed.

No Expo Go? Open in your browser

A two-sided card that turns over in 3D, lifting toward you half-way through.

<FlipCard />4 variantsiOS · Android · Expo Go
9:41

Sunday 18 October

9:41

Installation#

One command copies the source into your app — you own the code and can change anything.

Usage#

Colours, radius and light / dark come from useKit(). Wrap your app in <KitProvider accent="#6366f1" radius={14}> to re-skin every component at once.

Props#

PropTypeDefault
front

First face. Sets the card's size.

ReactNode—
back

Second face, filling the same box.

ReactNode—
flipped

Controlled side — true shows the back.

boolean—
defaultFlipped

Uncontrolled initial side.

booleanfalse
onFlip

Fires when tapped.

(flipped: boolean) => void—
direction

Turn around the vertical or the horizontal axis.

"horizontal" | "vertical""horizontal"
trigger

Flip on tap, or only through `flipped`.

"press" | "none""press"
perspective

Depth of the effect — smaller is more dramatic.

number1000
radius

Corner radius of both faces.

number20
shadow

Soft shadow that narrows as the card turns edge-on.

booleantrue
accessibilityLabel

Label for screen readers.

string—
labels

Screen-reader hints for each side.

{ toBack?: string; toFront?: string }English hints
style

Style for the root (width, margins).

StyleProp<ViewStyle>—

Behaviour & accessibility#

Touch
Tap to flip. With trigger 'none' it only turns when you change flipped from outside.
Motion
A gentle spring turns the card around its vertical or horizontal axis while the shadow beneath narrows as it goes edge-on. Faces swap exactly at 90° so Android never flickers.
Screen readers
The card is a button whose hint says which side is showing, with an expanded state when flipped. The hidden face is hidden from screen readers.

Source#

PreviousCube PagerNextGift Card

Did you like the content?

Coding agents

Chat apps

Other

9:41

Polaroid

Flip Card

Manual installation
  1. Copy the files

    Create each file at the same path and paste its code. @/ points at your src/ folder (the default in new Expo apps). Skip any file you already have, like lib/kit.tsx.

    • src/components/ui/flip-card.tsx206 lines
    • src/lib/kit.tsx220 lines

    Want to read it first? Every file is in Source below.

  2. Use it

    App.tsx
    import { FlipCard } from "@/components/ui/flip-card";

    See Usage for a full example.

Example.tsx
import { FlipCard } from '@/components/ui/flip-card'; <FlipCard  accessibilityLabel="Boarding pass"  front={<PassFront flight="LH 402" />}  back={<PassBarcode code="ZK7Q2P" />}/>

Every variant above has a complete, runnable example in its Code tab.

src/components/ui/flip-card.tsx
import React, { useEffect, useMemo, useRef, useState } from 'react';import { Animated, Pressable, StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native'; import { motion } from '@/lib/kit'; export type FlipDirection = 'horizontal' | 'vertical';export type FlipTrigger = 'press' | 'none'; export interface FlipCardProps {  /** Shown first. Sets the card's size — the back fills the same box. */  front: React.ReactNode;  back: React.ReactNode;  /** Controlled side: `true` shows the back. */  flipped?: boolean;  /** Uncontrolled initial side (default false). */  defaultFlipped?: boolean;  onFlip?: (flipped: boolean) => void;  /** Spin around the vertical axis (left/right) or the horizontal one (top/bottom). */  direction?: FlipDirection;  /** `press` flips on tap; `none` leaves it to `flipped` (e.g. a button elsewhere). */  trigger?: FlipTrigger;  /** Depth of the 3D effect — smaller is more dramatic (default 1000). */  perspective?: number;  /** Corner radius of both faces (default 20). */  radius?: number;  /** Soft shadow that narrows as the card turns edge-on (default true). */  shadow?: boolean;  /**   * Drive the turn from outside (0 = front, 1 = back) — from `useFlipProgress`, when something else   * must follow the flip, like a TiltCard's shadow. `flipped` still sets the side for accessibility.   */  progress?: Animated.Value;  /** Accessibility label; the hint says which side is showing. */  accessibilityLabel?: string;  /** Screen-reader hints (defaults in English). */  labels?: { toBack?: string; toFront?: string };  style?: StyleProp<ViewStyle>;} // How wide the shadow under a flipping card is through the turn: roughly cos θ, always a little// narrower than the card so a flat shadow plate never shows past it, and none at all edge-on.const SQUEEZE = {  inputRange: [0, 0.25, 0.45, 0.5, 0.55, 0.75, 1],  outputRange: [1, 0.6, 0.12, 0, 0.12, 0.6, 1],}; /** * The flip as an animated 0 → 1 value, springing like FlipCard's own, plus `shadowScaleX` — pass * `progress` to the FlipCard and `shadowScaleX` to a TiltCard wrapped round it, so the tilt * shadow narrows as the card turns edge-on instead of showing as a dark box behind it. */export function useFlipProgress(flipped: boolean) {  const progress = useRef(new Animated.Value(flipped ? 1 : 0)).current;  useEffect(() => {    Animated.spring(progress, { toValue: flipped ? 1 : 0, ...motion.gentle, useNativeDriver: true }).start();  }, [flipped, progress]);  const shadowScaleX = useMemo(() => progress.interpolate(SQUEEZE), [progress]);  return { progress, shadowScaleX };} /** * A card with two faces that flips in 3D on tap — it lifts toward you half-way through and its * shadow narrows as it turns edge-on, like a real card turning over. Controlled or uncontrolled. */export function FlipCard({  front,  back,  flipped: flippedProp,  defaultFlipped = false,  onFlip,  direction = 'horizontal',  trigger = 'press',  perspective = 1000,  radius = 20,  shadow = true,  progress,  accessibilityLabel,  labels,  style,}: FlipCardProps) {  const controlled = flippedProp !== undefined;  const [own, setOwn] = useState(defaultFlipped);  const flipped = controlled ? flippedProp : own;   const turn = useFlipProgress(flipped).progress;  const p = progress ?? turn;   const toggle = () => {    const next = !flipped;    if (!controlled) setOwn(next);    onFlip?.(next);  };   const horizontal = direction === 'horizontal';  const axis = horizontal ? 'rotateY' : 'rotateX';  const frontTurn = p.interpolate({    inputRange: [0, 1],    outputRange: ['0deg', '180deg'],  });  const backTurn = p.interpolate({    inputRange: [0, 1],    outputRange: ['180deg', '360deg'],  });  // Lift toward the viewer half-way through the turn.  const lift = p.interpolate({    inputRange: [0, 0.5, 1],    outputRange: [1, 1.05, 1],  });  // Swap faces exactly at 90° — backfaceVisibility alone flickers on some Android versions.  const frontOpacity = p.interpolate({    inputRange: [0, 0.499, 0.5, 1],    outputRange: [1, 1, 0, 0],  });  const backOpacity = p.interpolate({    inputRange: [0, 0.499, 0.5, 1],    outputRange: [0, 0, 1, 1],  });   const face = (turn: Animated.AnimatedInterpolation<string>, opacity: Animated.AnimatedInterpolation<number>) =>    ({      borderRadius: radius,      opacity,      transform: [{ perspective }, { [axis]: turn } as never, { scale: lift }],    }) as Animated.WithAnimatedValue<ViewStyle>;   // The shadow squeezes along the turning axis as the card goes edge-on (see SQUEEZE) and fades a little.  const squeeze = p.interpolate(SQUEEZE);   const body = (    <View>      {shadow ? (        <Animated.View          pointerEvents="none"          style={[            styles.plate,            {              borderRadius: radius,              opacity: p.interpolate({                inputRange: [0, 0.5, 1],                outputRange: [1, 0.4, 1],              }),              transform: [{ scale: 0.9 }, horizontal ? { scaleX: squeeze } : { scaleY: squeeze }],            },          ]}        />      ) : null}      {/* Faces live in their own (non-collapsing) layer: iOS intersects sibling layers in 3D, so a          turning face next to the flat shadow plate would be sliced by it. */}      {/* On Android the wrapper carries the elevation that keeps the faces above the plate; the faces          get none, since a face turned in 3D would cast a warped shadow wedge below the card. */}      <View collapsable={false} style={{ elevation: shadow ? 23 : 0 }}>        {/* The hidden face stays mounted (for the turn) but is hidden from screen readers. */}        <Animated.View          style={[styles.face, face(frontTurn, frontOpacity)]}          pointerEvents={flipped ? 'none' : 'auto'}          accessibilityElementsHidden={flipped}          importantForAccessibility={flipped ? 'no-hide-descendants' : 'auto'}        >          {front}        </Animated.View>        <Animated.View          style={[styles.face, styles.back, face(backTurn, backOpacity)]}          pointerEvents={flipped ? 'auto' : 'none'}          accessibilityElementsHidden={!flipped}          importantForAccessibility={flipped ? 'auto' : 'no-hide-descendants'}        >          {back}        </Animated.View>      </View>    </View>  );   if (trigger === 'none') return <View style={style}>{body}</View>;  return (    <Pressable      onPress={toggle}      accessibilityRole="button"      accessibilityLabel={accessibilityLabel}      accessibilityHint={flipped ? (labels?.toFront ?? 'Showing the back. Tap to flip to the front.') : (labels?.toBack ?? 'Tap to flip to the back.')}      accessibilityState={{ expanded: flipped }}      style={style}    >      {body}    </Pressable>  );} const styles = StyleSheet.create({  face: { overflow: 'hidden', backfaceVisibility: 'hidden' },  back: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 },  plate: {    position: 'absolute',    top: 0,    left: 0,    right: 0,    bottom: 0,    backgroundColor: '#000',    shadowColor: '#000',    shadowOpacity: 0.4,    shadowRadius: 20,    shadowOffset: { width: 0, height: 14 },    // High enough that Android's shadow spreads past the inset plate (iOS uses the shadow* props).    elevation: 22,  },});