About this animation
A world clock with cropped numeral bands and a rotating city wheel, with configurable cities, labels, colors, and fonts.
Why this one
World clocks usually bury small times in a list. This clock makes time the poster: choosing a city turns all three bands in a staggered mechanical sequence. Warm paper, tonal numerals, and a single glowing orange city marker give the demo its own identity. Northwind is a fictional wordmark from the demo brand kit.
How it works
- The host provides cities with IANA time zones or fixed UTC offsets in minutes. IANA formatting respects daylight saving time; an invalid zone falls back to the supplied offset, or UTC.
- A one-second timer calculates the local digits and updates six shared values. Numeral text is already mounted, so React does not render numeral text on each tick. Only the hidden accessible time changes once a second; the period label changes at noon and midnight.
- Each column translates through two copies of the ten digits, fades by distance, and normalizes to the exact final digit. Each band clips the giant glyphs intentionally, with 1.1× line height.
- Dragging controls one continuous wheel position. Release projects velocity to an index, then an interruptible, critically damped spring snaps to that city. Tap a neighboring name or use the grid control to select without dragging.
- A city change rolls hours, minutes, and seconds with 45 ms row delays. Changing hour format rolls only the hours unless a normal clock carry happens at the same time.
- Reduced motion uses 200 ms numeral fades, immediate wheel placement, and a fading city title. All timers and numeral animations are cleaned up.
Install
npx expo install @shopify/react-native-skia expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsValidated dependency versions: Skia 2.14.0, LinearGradient ~58.0.3, Gesture Handler ~3.3.0, Reanimated 4.7.1, Worklets 0.13.0. Wrap your screen in GestureHandlerRootView. For web, initialize Skia/CanvasKit before mounting this component, following the Skia web setup; this repository's viewer uses a withSkia lazy wrapper. Icons: Lucide (ISC), copied from the project kit.
Usage
import { SlicedWorldClock } from './sliced-world-clock';
const cities = [
{ id: 'lis', name: 'Lisbon', country: 'Portugal', abbreviation: 'LIS', timeZone: 'Europe/Lisbon' },
{ id: 'tokyo', name: 'Tokyo', country: 'Japan', abbreviation: 'TOKYO', timeZone: 'Asia/Tokyo' },
];
<SlicedWorldClock cities={cities} city="lis" />;const clock = useRef<SlicedWorldClockRef>(null);
<SlicedWorldClock
ref={clock}
cities={cities}
brand="Your world"
colorScheme="auto"
duration={400}
fontFamily="YourInterfaceFont"
numeralFontFamily="YourLightFont"
monoFontFamily="YourMonoFont"
colors={{ accent: '#F28705' }}
onCityChange={(id) => saveSelectedCity(id)}
accessibilityLabel={(city, time) => `${city.name}, local time ${time}`}
/>;
// In an event handler:
clock.current?.setCity('tokyo');
clock.current?.setFormat('24h');Load custom fonts in your host. The component has no theme imports or assets outside its folder. The catalog demo passes Geist Regular, Geist Light, and Geist Mono.
Props
| Prop | Type | Default | Purpose |
|---|---|---|---|
| cities | readonly ClockCity[] | required | Ordered wheel entries; IDs must be unique |
| city | string | first city | External selection; ref and user input also select internally |
| onCityChange | (id: string) => void | — | Semantic selection callback |
| format | '12h' | '24h' | '12h' | Initial/external hour format |
| onFormatChange | (format) => void | — | Format callback |
| onInteraction | () => void | — | Touch-start hook for stopping host autoplay |
| brand | string | 'World clock' | Header wordmark |
| colors | Partial<ClockColors> | warm light palette | Background, ink, hours, minutes, rule, accent, surface, selectedText |
| colorScheme | 'light' | 'dark' | 'auto' | 'light' | Complete base palette |
| fontFamily | string | system | UI font |
| numeralFontFamily | string | system | Giant numeral font |
| monoFontFamily | string | system | Wheel and labels font |
| labels | object | h, min, sec, AM, PM, 12h, 24h | hours, minutes, seconds, am, pm, twelveHour, twentyFourHour, wheel, hint, grid, empty |
| accessibilityLabel | (city, time) => string | city and local time | Accessible clock text formatter |
| now | () => number | Date.now | Timestamp source |
| duration | number | 450 | Automatic roll duration in milliseconds |
| style | StyleProp<ViewStyle> | — | Root style |
| testID | string | 'sliced-world-clock' | Root ID; -next, -12h, -24h, -wheel suffixes |
ClockCity: id, name, country, abbreviation, optional timeZone, optional utcOffsetMinutes. An empty list leaves the wheel inert. Keep abbreviations short and the list stable. For controlled integration, mirror the callbacks into city and format.
Ref methods: setCity(id) (unknown IDs ignored), setFormat('12h' | '24h').
Layout and demo
The root fills its host and measures with onLayout. It caps content width at 520 pt and uses a compact layout below 520 pt height. Numerals use the smaller of width and available height. The host applies safe-area insets; the component does not add them. Below 350 pt width, the controls move onto a second header row. Content scrolls when text scaling needs extra space. Header controls reserve 40 pt on the right for the catalog viewer's close button. Controls are 48×48 pt; the wheel is at least 90 pt tall.
The autoplay loop starts in Lisbon, turns to Tokyo at 2 s and NYC at 5 s, switches to 24h at 6.2 s, returns to Lisbon at 8.5 s and 12h at 10 s, and repeats at 12 s. User touch stops the loop; the real clock continues running.
Compatibility
Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, Skia 2.14.0. Uses Expo Go bundled modules. Web requires CanvasKit initialization. Supports iOS, Android, and web.
Accessibility
All controls have button roles, labels, selected states, and activation actions. The wheel exposes an adjustable role with increment/decrement actions and tap alternatives. Selection and format changes are announced; second ticks are not live announcements. The hidden clock label includes the complete time. Reduced motion removes spatial transitions and replaces numeral changes with a 200 ms fade. Intentional giant glyph cropping is part of the pattern; the accessible time remains complete.




