Ruler-style timeline for plotting milestones along a horizontal or vertical axis.
Age
Years
0
2000
Born in Can Tho, Viet Nam.
1
2001
2
2002
3
2003
4
2004
5
2005
6
2006
Started at Thuan Hung Primary School.
7
2007
8
2008
9
2009
10
2010
11
2011
Started at Thuan Hung Secondary School.
12
2012
13
2013
14
2014
Started learning to code and built my first website.
Visited Ha Noi, the capital, for the first time.
15
2015
Won awards:
- 3rd Prize — Can Tho City Young Informatics Contest 2015
16
2016
Won awards:
- Consolation Prize — Can Tho City Young Informatics Contest 2016
Visited Quy Nhon, Binh Dinh for the first time, and returned to Ha Noi.
17
2017
Won awards:
- 2nd Prize — Can Tho City Outstanding Student Selection Exam 2016-2017
- Consolation Prize — Can Tho City Young Informatics Contest 2017
- Creative Award — Binh Duong Hackathon 2017
18
2018
Won awards:
- 1st Prize — Can Tho City Science and Engineering Fair 2018
- 3rd Prize — Can Tho City Outstanding Student Selection Exam 2017-2018
Began freelancing and joined Tung Tung as a UI/UX Designer.
19
2019
Became a Mobile Developer at Tung Tung.
Won 2nd Prize — Business Startup Competition 2019.
20
2020
Became a Web Developer at Tung Tung.
21
2021
22
2022
Joined Simplamo as a Senior Frontend Developer and UI Lead.
Launched — 80k+ downloads, 30k+ active users.
Won Bronze Medal — 10th Design, Manufacturing, and Application Award 2022.
23
2023
24
2024
Founded .
25
2025
Open-sourced — 2.2k+ stars on GitHub.
Released — 50k+ weekly downloads, selected for the .
Followed by on X.
26
2026
Joined as a Design Engineer.
Selected for the .
1import { Markdown } from "@/components/ui/pxl/markdown";2import {3 TimescaleAge,4 TimescaleContent,5 TimescaleHeader,6 TimescaleIntroScroll,7 TimescaleItem,8 TimescaleRail,9 TimescaleRoot,10 TimescaleTick,11 TimescaleTrack,12 TimescaleViewport,13 TimescaleYear,14} from "@/components/ui/pxl/timescale";15
16const BIRTH_YEAR = 2000;17
18const MILESTONES: {19 year: number;20 content?: string;21}[] = [22 {23 year: 2000,24 content: "Born in Can Tho, Viet Nam.",25 },26 { year: 2001 },27 { year: 2002 },28 { year: 2003 },29 { year: 2004 },30 { year: 2005 },31 {32 year: 2006,33 content: "Started at Thuan Hung Primary School.",34 },35 { year: 2007 },36 { year: 2008 },37 { year: 2009 },38 { year: 2010 },39 {40 year: 2011,41 content: "Started at Thuan Hung Secondary School.",42 },43 { year: 2012 },44 { year: 2013 },45 {46 year: 2014,47 content: `Started learning to code and built my first website.48
49Visited Ha Noi, the capital, for the first time.`,50 },51 {52 year: 2015,53 content: `Won awards:54
55- 3rd Prize — Can Tho City Young Informatics Contest 201556`,57 },58 {59 year: 2016,60 content: `Won awards:61
62- Consolation Prize — Can Tho City Young Informatics Contest 201663
64Visited Quy Nhon, Binh Dinh for the first time, and returned to Ha Noi.`,65 },66 {67 year: 2017,68 content: `Won awards:69
70- 2nd Prize — Can Tho City Outstanding Student Selection Exam 2016-201771- Consolation Prize — Can Tho City Young Informatics Contest 201772- Creative Award — Binh Duong Hackathon 2017`,73 },74 {75 year: 2018,76 content: `Won awards:77
78- 1st Prize — Can Tho City Science and Engineering Fair 201879- 3rd Prize — Can Tho City Outstanding Student Selection Exam 2017-201880
81Began freelancing and joined Tung Tung as a UI/UX Designer.82`,83 },84 {85 year: 2019,86 content: `Became a Mobile Developer at Tung Tung.87
88Won 2nd Prize — Business Startup Competition 2019.`,89 },90 {91 year: 2020,92 content: "Became a Web Developer at Tung Tung.",93 },94 { year: 2021 },95 {96 year: 2022,97 content: `Joined Simplamo as a Senior Frontend Developer and UI Lead.98
99Launched [ZaDark](https://zadark.com) — 80k+ downloads, 30k+ active users.100
101Won Bronze Medal — 10th Design, Manufacturing, and Application Award 2022.`,102 },103 { year: 2023 },104 {105 year: 2024,106 content: "Founded [Quaric](https://quaric.com).",107 },108 {109 year: 2025,110 content: `Open-sourced [chanhdai.com](https://github.com/ncdai/chanhdai.com) — 2.2k+ stars on GitHub.111
112Released [React Wheel Picker](https://react-wheel-picker.chanhdai.com) — 50k+ weekly downloads, selected for the [Vercel OSS Program](https://vercel.com/open-source-program).113
114Followed by [shadcn](https://x.com/shadcn) on X.`,115 },116 {117 year: 2026,118 content: `Joined [shadcncraft](https://shadcncraft.com) as a Design Engineer.119
120Selected for the [Claude for Open Source Program](https://claude.com/contact-sales/claude-for-oss).`,121 },122];123
124export default function TimescaleDemo() {125 return (126 <TimescaleIntroScroll>127 <TimescaleRoot className="mt-4">128 <TimescaleHeader>129 <TimescaleAge>Age</TimescaleAge>130 <TimescaleYear>Years</TimescaleYear>131 </TimescaleHeader>132
133 <TimescaleViewport>134 <TimescaleTrack>135 <TimescaleRail />136
137 {MILESTONES.map((milestone) => (138 <TimescaleItem key={milestone.year}>139 <TimescaleTick />140
141 <TimescaleAge>{milestone.year - BIRTH_YEAR}</TimescaleAge>142 <TimescaleYear>{milestone.year}</TimescaleYear>143
144 {milestone.content && (145 <TimescaleContent>146 <Markdown>{milestone.content}</Markdown>147 </TimescaleContent>148 )}149 </TimescaleItem>150 ))}151 </TimescaleTrack>152 </TimescaleViewport>153 </TimescaleRoot>154 </TimescaleIntroScroll>155 );156}Installation
Section titled “Installation”pnpm dlx shadcn@latest add pxl-ui/registry/timescale
Copy and paste the following code into your project.
1import { type ComponentProps, useEffect, useRef } from "react";2
3import { cn } from "@/lib/utils";4
5const INTRO_SCROLL_START_HOLD = 200; // ms resting on the first item before scrolling6
7function TimescaleRoot({8 className,9 orientation = "horizontal",10 ...props11}: ComponentProps<"div"> & {12 orientation?: "horizontal" | "vertical";13}) {14 return (15 <div16 data-slot="timescale-root"17 data-orientation={orientation}18 className={cn(19 "group/timescale relative flex w-full [--timescale-rail:--spacing(14)]",20 "data-[orientation=vertical]:flex-col",21 className,22 )}23 {...props}24 />25 );26}27
28function TimescaleViewport({ className, ...props }: ComponentProps<"div">) {29 return (30 <div31 data-slot="timescale-viewport"32 className={cn(33 "no-scrollbar w-full",34 "group-data-[orientation=horizontal]/timescale:flex group-data-[orientation=horizontal]/timescale:flex-1 group-data-[orientation=horizontal]/timescale:scroll-fade-x group-data-[orientation=horizontal]/timescale:overflow-x-auto group-data-[orientation=horizontal]/timescale:overscroll-x-contain group-data-[orientation=horizontal]/timescale:pl-20 group-data-[orientation=horizontal]/timescale:scroll-fade-s-40",35 className,36 )}37 {...props}38 />39 );40}41
42function TimescaleHeader({ className, ...props }: ComponentProps<"div">) {43 return (44 <div45 data-slot="timescale-header"46 aria-hidden="true"47 className={cn(48 "z-10",49 "group-data-[orientation=horizontal]/timescale:absolute group-data-[orientation=horizontal]/timescale:top-0 group-data-[orientation=horizontal]/timescale:left-0 group-data-[orientation=horizontal]/timescale:w-20 group-data-[orientation=horizontal]/timescale:shrink-0 group-data-[orientation=horizontal]/timescale:bg-linear-to-r group-data-[orientation=horizontal]/timescale:from-background group-data-[orientation=horizontal]/timescale:from-75% group-data-[orientation=horizontal]/timescale:to-transparent group-data-[orientation=horizontal]/timescale:pr-4 group-data-[orientation=horizontal]/timescale:text-right",50 "group-data-[orientation=vertical]/timescale:grid group-data-[orientation=vertical]/timescale:w-full group-data-[orientation=vertical]/timescale:grid-cols-[var(--timescale-rail)_1fr] group-data-[orientation=vertical]/timescale:gap-x-4 group-data-[orientation=vertical]/timescale:bg-background",51 className,52 )}53 {...props}54 />55 );56}57
58function TimescaleTrack({ className, ...props }: ComponentProps<"div">) {59 return (60 <div61 data-slot="timescale-track"62 className={cn(63 "relative flex",64 "group-data-[orientation=horizontal]/timescale:w-max group-data-[orientation=horizontal]/timescale:items-start",65 "group-data-[orientation=vertical]/timescale:w-full group-data-[orientation=vertical]/timescale:flex-col group-data-[orientation=vertical]/timescale:pt-4",66 className,67 )}68 {...props}69 />70 );71}72
73function TimescaleRail({ className, ...props }: ComponentProps<"div">) {74 return (75 <div76 data-slot="timescale-rail"77 aria-hidden="true"78 className={cn(79 "pointer-events-none absolute border-dashed",80 "group-data-[orientation=horizontal]/timescale:inset-x-0 group-data-[orientation=horizontal]/timescale:top-(--timescale-rail) group-data-[orientation=horizontal]/timescale:h-px group-data-[orientation=horizontal]/timescale:border-t",81 "group-data-[orientation=vertical]/timescale:inset-y-0 group-data-[orientation=vertical]/timescale:left-(--timescale-rail) group-data-[orientation=vertical]/timescale:w-px group-data-[orientation=vertical]/timescale:border-l",82 className,83 )}84 {...props}85 />86 );87}88
89function TimescaleItem({ className, ...props }: ComponentProps<"div">) {90 return (91 <div92 data-slot="timescale-item"93 className={cn(94 "relative",95 "group-data-[orientation=horizontal]/timescale:w-20 group-data-[orientation=horizontal]/timescale:shrink-0 group-data-[orientation=horizontal]/timescale:not-last:pr-4 group-data-[orientation=horizontal]/timescale:has-data-[slot=timescale-content]:w-80",96 "group-data-[orientation=vertical]/timescale:grid group-data-[orientation=vertical]/timescale:w-full group-data-[orientation=vertical]/timescale:grid-cols-[var(--timescale-rail)_1fr] group-data-[orientation=vertical]/timescale:gap-x-4 group-data-[orientation=vertical]/timescale:not-last:pb-4",97 className,98 )}99 {...props}100 />101 );102}103
104function TimescaleTick({ className, ...props }: ComponentProps<"span">) {105 return (106 <span107 data-slot="timescale-tick"108 aria-hidden="true"109 className={cn(110 "absolute z-10 bg-border",111 "group-data-[orientation=horizontal]/timescale:top-(--timescale-rail) group-data-[orientation=horizontal]/timescale:left-0 group-data-[orientation=horizontal]/timescale:h-3 group-data-[orientation=horizontal]/timescale:w-px group-data-[orientation=horizontal]/timescale:-translate-y-1/2",112 "group-data-[orientation=vertical]/timescale:top-2.5 group-data-[orientation=vertical]/timescale:left-(--timescale-rail) group-data-[orientation=vertical]/timescale:h-px group-data-[orientation=vertical]/timescale:w-3 group-data-[orientation=vertical]/timescale:-translate-x-1/2",113 className,114 )}115 {...props}116 />117 );118}119
120function TimescaleAge({ className, ...props }: ComponentProps<"p">) {121 return (122 <p123 data-slot="timescale-age"124 className={cn(125 "text-xs leading-5 font-medium text-muted-foreground tabular-nums",126 "in-data-[slot=timescale-header]:tracking-widest in-data-[slot=timescale-header]:uppercase",127 "group-data-[orientation=vertical]/timescale:col-start-1 group-data-[orientation=vertical]/timescale:row-start-1 group-data-[orientation=vertical]/timescale:pr-4 group-data-[orientation=vertical]/timescale:text-right",128 className,129 )}130 {...props}131 />132 );133}134
135function TimescaleYear({ className, ...props }: ComponentProps<"p">) {136 return (137 <p138 data-slot="timescale-year"139 className={cn(140 "text-xs leading-5 font-medium text-muted-foreground tabular-nums",141 "in-data-[slot=timescale-header]:tracking-widest in-data-[slot=timescale-header]:uppercase",142 "group-data-[orientation=vertical]/timescale:col-start-2 group-data-[orientation=vertical]/timescale:row-start-1",143 className,144 )}145 {...props}146 />147 );148}149
150function TimescaleContent({ className, ...props }: ComponentProps<"div">) {151 return (152 <div153 data-slot="timescale-content"154 className={cn(155 "w-full py-4 text-left",156 "group-data-[orientation=horizontal]/timescale:mt-4",157 "group-data-[orientation=vertical]/timescale:col-start-2 group-data-[orientation=vertical]/timescale:row-start-2",158 className,159 )}160 {...props}161 />162 );163}164
165// Sweeps a horizontal Timescale to its most recent item on mount using native166// smooth scroll, so `scroll-fade` stays in sync and there is no scroll stepping.167function TimescaleIntroScroll({ children }: { children: React.ReactNode }) {168 const ref = useRef<HTMLDivElement>(null);169
170 useEffect(() => {171 const viewport = ref.current?.querySelector<HTMLElement>(172 '[data-slot="timescale-viewport"]',173 );174 if (!viewport) return;175
176 const distance = viewport.scrollWidth - viewport.clientWidth;177 if (distance <= 0) return;178
179 // Respect users who opt out of motion: land at the end without the sweep.180 if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {181 viewport.scrollLeft = distance;182 return;183 }184
185 // A user gesture cancels the native smooth scroll, so no manual interruption186 // handling is needed.187 const timer = window.setTimeout(() => {188 viewport.scrollTo({ left: distance, behavior: "smooth" });189 }, INTRO_SCROLL_START_HOLD);190
191 return () => window.clearTimeout(timer);192 }, []);193
194 return (195 <div ref={ref} className="contents">196 {children}197 </div>198 );199}200
201export {202 TimescaleAge,203 TimescaleContent,204 TimescaleHeader,205 TimescaleIntroScroll,206 TimescaleItem,207 TimescaleRail,208 TimescaleRoot,209 TimescaleTick,210 TimescaleTrack,211 TimescaleViewport,212 TimescaleYear,213};Update the import paths to match your project setup.
import { TimescaleAge, TimescaleContent, TimescaleHeader, TimescaleIntroScroll, TimescaleItem, TimescaleRail, TimescaleRoot, TimescaleTick, TimescaleTrack, TimescaleViewport, TimescaleYear,} from "@/components/ui/pxl/timescale";<TimescaleIntroScroll> <TimescaleRoot> <TimescaleHeader> <TimescaleAge /> <TimescaleYear /> </TimescaleHeader>
<TimescaleViewport> <TimescaleTrack> <TimescaleRail />
<TimescaleItem> <TimescaleTick /> <TimescaleAge /> <TimescaleYear /> <TimescaleContent /> </TimescaleItem> </TimescaleTrack> </TimescaleViewport> </TimescaleRoot></TimescaleIntroScroll>Composition
Section titled “Composition”Use the following composition to build a Timescale
TimescaleRoot├── TimescaleHeader│ ├── TimescaleAge│ └── TimescaleYear└── TimescaleViewport └── TimescaleTrack ├── TimescaleRail └── TimescaleItem ├── TimescaleTick ├── TimescaleAge ├── TimescaleYear └── TimescaleContentWrap in TimescaleIntroScroll to scroll a horizontal Timescale to its latest item on mount, respecting prefers-reduced-motion.
API reference
Section titled “API reference”TimescaleRoot
Section titled “TimescaleRoot”| Prop | Type | Default | Description |
|---|---|---|---|
orientation |
string |
"horizontal" | "vertical" |