I’m currently trying to implement a Timeline React component based on shadcn. This component is a community-contributed component which I admittedly just copy-pasted from GitHub. I was testing only on Chromium browsers and it looked fine even on mobile. But the moment I deployed it via Vercel, the timeline component immediately broke and looked different across browsers.
This is what it looks like on Chrome:
Chrome Image
And this is what it looks like on Safari:
Safari Image
My first instinct thought that it was an issue with the TimelineHeading height which causes it to push down all the elements below it. Further checking though, the dot seems to have a larger space on top and below in Safari compared to Chrome.
For more context, the app was made using Astro and the component was created with React. Doing client:only="react"
doesn’t work also since it just breaks the layout further. If anyone could point me in the right direction that would be great. Thanks!
History.astro
---
import { Image } from "astro:assets";
import {
Timeline,
TimelineContent,
TimelineDot,
TimelineHeading,
TimelineItem,
TimelineLine,
} from "@/components/ui/timeline";
import gihay_launch from "@/assets/about/gihay_launch.jpeg";
import gihay_first_workshop from "@/assets/about/gihay_first_workshop.jpeg";
import gihay_thousand_cores from "@/assets/about/gihay_thousand_cores.jpeg";
import gihay_seventy from "@/assets/about/gihay_seventy.jpeg";
import gihay_ramon_distribution from "@/assets/about/gihay_ramon_distribution.jpg";
import gihay_purras_distribution from "@/assets/about/gihay_purras_distribution.jpeg";
import gihay_hundred from "@/assets/about/gihay_hundred.jpeg";
import gihay_calma_distribution from "@/assets/about/gihay_calma_distribution.jpeg";
import gihay_umapad_distribution from "@/assets/about/gihay_umapad_distribution.jpeg";
import gihay_my_refuge from "@/assets/about/gihay_my_refuge.png";
---
<section class="h-full flex flex-col bg-gihay-bg py-10 gap-10 items-center">
<div class="flex justify-center h-fit gap-4">
<span
class="font-extrabold text-7xl md:text-8xl lg:text-9xl text-gihay-dark-pink"
>Origin</span
>
<div class="flex flex-col justify-end text-4xl md:text-5xl lg:text-6xl">
<span class="font-bright text-gihay-dark-pink">Gihay</span>
<span class="font-bright text-gihay-dark-pink">Initiative</span>
</div>
</div>
<div class="w-11/12 md:w-9/12">
<Timeline positions="center">
<TimelineItem status="done">
<TimelineHeading
className="text-gihay-dark-pink text-lg md:text-xl lg:text-2xl font-semibold"
side="left">March 3, 2022</TimelineHeading
>
<TimelineDot status="done" />
<TimelineLine done />
<TimelineContent
className="text-gihay-text text-sm md:text-md lg:text-xl"
side="left"
>
Launched during Women's Month, the Gihay Initiative empowers
underprivileged menstruating individuals in Cebu by providing them
with sustainable, reusable napkins. This initiative, spearheaded by
passionate Carolinian youth, aims to address menstrual hygiene
accessibility while promoting environmental sustainability.
</TimelineContent>
<TimelineContent side="right">
<Image
src={gihay_launch}
class="h-[20vh] shadow-xl object-cover object-top rounded-xl"
alt="Gihay launch picture"
/>
</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineHeading
className="text-gihay-dark-pink text-lg md:text-xl lg:text-2xl font-semibold"
side="right">June 22 - July 7, 2022</TimelineHeading
>
<TimelineDot status="done" />
<TimelineLine done />
<TimelineContent
className="text-gihay-text text-sm md:text-md lg:text-xl"
side="right"
>
Over three weeks, the Gihay Initiative held its first Volunteer
Workshop and Orientation, attracting 35 dynamic volunteers committed
to the cause. These sessions included training on the production and
distribution of reusable menstrual products, reinforcing our
community's commitment to women's health.
</TimelineContent>
<TimelineContent side="left">
<Image
src={gihay_first_workshop}
class="h-[20vh] shadow-xl object-cover object-top rounded-xl"
alt="Gihay first workshop picture"
quality="max"
/>
</TimelineContent>
</TimelineItem>
<TimelineItem status="done">
<TimelineHeading
className="text-gihay-dark-pink text-lg md:text-xl lg:text-2xl font-semibold"
side="left">August 11, 2022</TimelineHeading
>
<TimelineDot status="done" />
<TimelineLine done />
<TimelineContent
className="text-gihay-text text-sm md:text-md lg:text-xl"
side="left"
>
A significant achievement: the organization alone crafted over 1,000
reusable cores, marking a major milestone in our commitment to
sustainability and support for menstrual health.
</TimelineContent>
<TimelineContent side="right">
<Image
src={gihay_thousand_cores}
class="h-[20vh] shadow-xl object-cover object-top rounded-xl"
alt="Gihay 1000 core milestone picture"
/>
</TimelineContent>
</TimelineItem>
<TimelineItem status="done">
<TimelineHeading
className="text-gihay-dark-pink text-lg md:text-xl lg:text-2xl font-semibold"
side="right">September 1, 2022</TimelineHeading
>
<TimelineDot status="done" />
<TimelineLine done />
<TimelineContent
className="text-gihay-text text-sm md:text-md lg:text-xl"
side="right"
>
The initiative received 70 kilograms of cloth donations and mobilized
40 dedicated youth volunteers. This substantial support has greatly
enhanced our production capabilities and outreach efforts.
</TimelineContent>
<TimelineContent side="left">
<Image
src={gihay_seventy}
class="h-[20vh] shadow-xl object-cover rounded-xl"
alt="Gihay 70 kg donation picture"
/>
</TimelineContent>
</TimelineItem>
<TimelineItem status="done">
<TimelineHeading
className="text-gihay-dark-pink text-lg md:text-xl lg:text-2xl font-semibold"
side="left">December 5, 2022</TimelineHeading
>
<TimelineDot status="done" />
<TimelineLine done />
<TimelineContent
className="text-gihay-text text-sm md:text-md lg:text-xl"
side="left"
>
Hosted a Period Poverty Awareness and Menstrual Health Seminar at
Ramon Duterte National High School to educate and empower students.
This event highlighted the importance of menstrual health and provided
valuable information to hundreds of students.
</TimelineContent>
<TimelineContent side="right">
<Image
src={gihay_ramon_distribution}
class="h-[20vh] shadow-xl object-cover rounded-xl"
alt="Gihay Ramon distribution picture"
/>
</TimelineContent>
</TimelineItem>
<TimelineItem status="done">
<TimelineHeading
className="text-gihay-dark-pink text-lg md:text-xl lg:text-2xl font-semibold"
side="right">December 19, 2022</TimelineHeading
>
<TimelineDot status="done" />
<TimelineLine done />
<TimelineContent
className="text-gihay-text text-sm md:text-md lg:text-xl"
side="right"
>
Expanded our educational outreach with a Menstrual Health Seminar in
Sitio Purras, emphasizing proper menstrual health practices. This
seminar provided in-depth education on menstrual health management,
impacting numerous lives in the community.
</TimelineContent>
<TimelineContent side="left">
<Image
src={gihay_purras_distribution}
class="h-[20vh] shadow-xl object-cover object-top rounded-xl"
alt="Gihay Ramon distribution picture"
/>
</TimelineContent>
</TimelineItem>
<TimelineItem status="done">
<TimelineHeading
className="text-gihay-dark-pink text-lg md:text-xl lg:text-2xl font-semibold"
side="left">January 21, 2023</TimelineHeading
>
<TimelineDot status="done" />
<TimelineLine done />
<TimelineContent
className="text-gihay-text text-sm md:text-md lg:text-xl"
side="left"
>
Continued our mission in Sitio Calma, conducting a seminar focused on
Period Poverty Awareness and promoting menstrual health awareness.
This seminar is part of our ongoing efforts to educate and support
underprivileged communities.
</TimelineContent>
<TimelineContent side="right">
<Image
src={gihay_calma_distribution}
class="h-[20vh] shadow-xl object-cover rounded-xl"
alt="Gihay Ramon distribution picture"
/>
</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineHeading
className="text-gihay-dark-pink text-lg md:text-xl lg:text-2xl font-semibold"
side="right">June 19, 2023</TimelineHeading
>
<TimelineDot status="done" />
<TimelineLine done />
<TimelineContent
className="text-gihay-text text-sm md:text-md lg:text-xl"
side="right"
>
Celebrating another major donation milestone with 100 kilograms of
cloth received, alongside the mobilization of 55 inspired youth
volunteers. These donations have been instrumental in expanding our
reach and increasing our impact.
</TimelineContent>
<TimelineContent side="left">
<Image
src={gihay_hundred}
class="h-[20vh] shadow-xl object-cover rounded-xl"
alt="Gihay Ramon distribution picture"
/>
</TimelineContent>
</TimelineItem>
<TimelineItem status="done">
<TimelineHeading
className="text-gihay-dark-pink text-lg md:text-xl lg:text-2xl font-semibold"
side="left">August 5, 2023</TimelineHeading
>
<TimelineDot status="done" />
<TimelineLine done />
<TimelineContent
className="text-gihay-text text-sm md:text-md lg:text-xl"
side="left"
>
Held a transformative Menstrual Health Seminar in Sitio Paglaum,
fostering discussions on menstrual health and hygiene. This event
furthered our goal of reducing period poverty and promoting health
education.
</TimelineContent>
<TimelineContent side="right">
<Image
src={gihay_umapad_distribution}
class="h-[20vh] shadow-xl object-cover rounded-xl"
alt="Gihay Ramon distribution picture"
/>
</TimelineContent>
</TimelineItem>
<TimelineItem status="done">
<TimelineHeading
className="text-gihay-dark-pink text-lg md:text-xl lg:text-2xl font-semibold"
side="right">January 19, 2024</TimelineHeading
>
<TimelineDot status="done" />
<TimelineContent
className="text-gihay-text text-sm md:text-md lg:text-xl"
side="right"
>
Successfully facilitated the turnover of 'With Thought of Twin Hearts'
and 'Priyanka Annuncia' projects to My Refuge House, marking a
significant collaboration. This partnership aims to amplify our
efforts and reach more beneficiaries.
</TimelineContent>
<TimelineContent side="left">
<Image
src={gihay_my_refuge}
class="h-[20vh] shadow-xl object-cover rounded-xl"
alt="Gihay Ramon distribution picture"
/>
</TimelineContent>
</TimelineItem>
</Timeline>
</div>
</section>
timeline.tsx
import React from "react";
import { type VariantProps, cva } from "class-variance-authority";
import { Check, Circle, X } from "lucide-react";
import { cn } from "@/lib/utils";
const timelineVariants = cva("flex flex-col items-stretch", {
variants: {
positions: {
left: "[&>li]:grid-cols-[0_min-content_1fr]",
right: "[&>li]:grid-cols-[1fr_min-content]",
center: "[&>li]:grid-cols-[1fr_min-content_1fr]",
},
},
defaultVariants: {
positions: "left",
},
});
interface TimelineProps
extends React.HTMLAttributes<HTMLUListElement>,
VariantProps<typeof timelineVariants> {}
const Timeline = React.forwardRef<HTMLUListElement, TimelineProps>(
({ children, className, positions, ...props }, ref) => {
return (
<ul
className={cn(timelineVariants({ positions }), className)}
ref={ref}
{...props}
>
{children}
</ul>
);
}
);
Timeline.displayName = "Timeline";
const timelineItemVariants = cva("grid gap-x-2", {
variants: {
status: {
done: "text-primary",
default: "text-muted-foreground",
},
},
defaultVariants: {
status: "default",
},
});
interface TimelineItemProps
extends React.HTMLAttributes<HTMLLIElement>,
VariantProps<typeof timelineItemVariants> {}
const TimelineItem = React.forwardRef<HTMLLIElement, TimelineItemProps>(
({ className, status, ...props }, ref) => (
<li
className={cn(timelineItemVariants({ status }), className)}
ref={ref}
{...props}
/>
)
);
TimelineItem.displayName = "TimelineItem";
const timelineDotVariants = cva(
"col-start-2 col-end-3 row-start-1 row-end-1 flex size-4 items-center justify-center rounded-full border border-current",
{
variants: {
status: {
default: "[&>*]:hidden",
current:
"[&>*:not(.lucide-circle)]:hidden [&>.lucide-circle]:fill-current [&>.lucide-circle]:text-current",
done: "bg-primary [&>*:not(.lucide-check)]:hidden [&>.lucide-check]:text-background",
error:
"border-destructive bg-destructive [&>*:not(.lucide-x)]:hidden [&>.lucide-x]:text-background",
custom: "[&>*:not(:nth-child(4))]:hidden [&>*:nth-child(4)]:block",
},
},
defaultVariants: {
status: "default",
},
}
);
interface TimelineDotProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof timelineDotVariants> {
customIcon?: React.ReactNode;
}
const TimelineDot = React.forwardRef<HTMLDivElement, TimelineDotProps>(
({ className, status, customIcon, ...props }, ref) => (
<div
role="status"
className={cn("m-auto h-fit", timelineDotVariants({ status }), className)}
ref={ref}
{...props}
>
<Circle className="size-2.5" />
<Check className="size-3" />
<X className="size-3" />
{customIcon}
</div>
)
);
TimelineDot.displayName = "TimelineDot";
const timelineContentVariants = cva(
"row-start-2 row-end-2 pb-8 text-muted-foreground",
{
variants: {
side: {
right: "col-start-3 col-end-4 mr-auto text-left",
left: "col-start-1 col-end-2 ml-auto text-right",
},
},
defaultVariants: {
side: "right",
},
}
);
interface TimelineContentProps
extends React.HTMLAttributes<HTMLParagraphElement>,
VariantProps<typeof timelineContentVariants> {}
const TimelineContent = React.forwardRef<
HTMLParagraphElement,
TimelineContentProps
>(({ className, side, ...props }, ref) => (
<p
className={cn(timelineContentVariants({ side }), className)}
ref={ref}
{...props}
/>
));
TimelineContent.displayName = "TimelineContent";
const timelineHeadingVariants = cva(
"row-start-1 row-end-1 line-clamp-1 max-w-full truncate h-fit",
{
variants: {
side: {
right: "col-start-3 col-end-4 mr-auto text-left",
left: "col-start-1 col-end-2 ml-auto text-right",
},
variant: {
primary: "text-base font-medium text-primary",
secondary: "text-sm font-light text-muted-foreground",
},
},
defaultVariants: {
side: "right",
variant: "primary",
},
}
);
interface TimelineHeadingProps
extends React.HTMLAttributes<HTMLParagraphElement>,
VariantProps<typeof timelineHeadingVariants> {}
const TimelineHeading = React.forwardRef<
HTMLParagraphElement,
TimelineHeadingProps
>(({ className, side, variant, ...props }, ref) => (
<p
role="heading"
aria-level={variant === "primary" ? 2 : 3}
className={cn(timelineHeadingVariants({ side, variant }), className)}
ref={ref}
{...props}
/>
));
TimelineHeading.displayName = "TimelineHeading";
interface TimelineLineProps extends React.HTMLAttributes<HTMLHRElement> {
done?: boolean;
}
const TimelineLine = React.forwardRef<HTMLHRElement, TimelineLineProps>(
({ className, done = false, ...props }, ref) => {
return (
<hr
role="separator"
aria-orientation="vertical"
className={cn(
"col-start-2 col-end-3 row-start-2 row-end-2 mx-auto flex h-full min-h-16 w-0.5 justify-center rounded-full",
done ? "bg-primary" : "bg-muted",
className
)}
ref={ref}
{...props}
/>
);
}
);
TimelineLine.displayName = "TimelineLine";
export {
Timeline,
TimelineDot,
TimelineItem,
TimelineContent,
TimelineHeading,
TimelineLine,
};