A zero-configuration React calendar component for displaying Google Calendar events in a responsive weekly view.
npm install cal7import { Calendar } from "cal7";
import "cal7/styles";
// Set environment variable: GOOGLE_CALENDAR_API_KEY=your_api_key
export default function MyApp() {
return <Calendar showSubscribeButton={true} />;
}Use a public Google Calendar ICS feed on the server, then render the client calendar with events. No Google API key required.
- Server: fetch the ICS and parse to events
// app/events/page.tsx (Next.js App Router, Server Component)
import { parseICS } from "cal7";
import "cal7/styles"; // one CSS import
async function getEventsFromICS() {
// Replace with your public Google Calendar ICS URL
const ICS_URL = "https://calendar.google.com/calendar/ical/your_calendar_id%40group.calendar.google.com/public/basic.ics";
const res = await fetch(ICS_URL, { next: { revalidate: 300 } }); // cache 5min
if (!res.ok) throw new Error("Failed to fetch ICS");
const icsText = await res.text();
return parseICS(icsText); // returns CalendarEvent[]
}
export default async function EventsPage() {
const events = await getEventsFromICS();
// Pass serializable events to the client
return <EventsCalendarClient events={events} />;
}- Client: render the calendar
// components/EventsCalendarClient.tsx ("use client")
"use client";
import { CalendarClient } from "cal7";
export default function EventsCalendarClient({ events }: { events: any[] }) {
return (
<CalendarClient
events={events}
locale="en-US"
timeZone="America/New_York"
calendarName="Our Events"
showSubscribeButton
titleAlignment="center"
/>
);
}- Theming in a few lines (optional)
// app/events/page.tsx (wrap client render)
import { ThemeProvider, type CalendarTheme } from "cal7";
import "cal7/styles";
const theme: CalendarTheme = {
colors: {
primary: "#059669", // brand green
today: "#059669",
todayBackground: "#D1FAE5",
eventBorder: "#A7F3D0",
},
};
export default async function EventsPage() {
const events = await getEventsFromICS();
return (
<ThemeProvider config={{ theme }}>
<EventsCalendarClient events={events} />
</ThemeProvider>
);
}Notes:
- parseICS focuses on VEVENT blocks, DATE vs DATETIME, Z vs local, and all‑day detection. Recurrence should be pre‑expanded by your provider (Google does this in ICS).
- For private calendars or advanced features (attendees, write access), use the API‑key flow described below.
- Server components: fetch/parse ICS or fetch via the Google API if you need private calendars.
- Client component: render with the normalized events.
- Styles:
import "cal7/styles"once (global or server layout), or rely on ThemeProvider tokens to map to CSS variables.
// app/layout.tsx
import "cal7/styles";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return <html><body>{children}</body></html>;
}Dropdown menus can render via a fixed‑position portal to avoid clipping inside modals or containers.
Props (1.2.0):
- AddToCalendarButton, SubscribeButton:
- portalTarget?: HTMLElement | string; defaults to document.body when provided
- dropdownPlacement?: "up" | "down" | "auto" (auto chooses based on available space)
- offset?: number (px), default 8
Example (subscribe opens upward by default):
<CalendarClient
events={events}
showSubscribeButton
/>Example (explicit portal to body for add‑to‑calendar in a modal):
import { AddToCalendarButton } from "cal7";
<AddToCalendarButton
event={event}
portalTarget="body"
dropdownPlacement="auto"
offset={10}
/>Cal7 maps theme tokens to CSS variables. You can set tokens via ThemeProvider or override CSS vars globally.
Colors (examples):
- primary, secondary, background, surface, text, textSecondary, textMuted
- border, borderLight
- today, todayBackground
- eventBackground, eventBorder, eventText
- headerEven, headerOdd, dayEven, dayOdd, dayHover
Other:
- typography: fontFamily, fontSize.{xs|sm|base|lg|xl|2xl}, fontWeight.{normal|medium|semibold|bold}
- spacing: { xs, sm, md, lg, xl, 2xl }
- borderRadius: { none, sm, md, lg, full }
- shadows: { none, sm, md, lg, xl }
- zIndex: { dropdown, modal, tooltip }
CSS example:
:root {
--cal7-color-primary: #059669;
--cal7-color-today: #059669;
--cal7-color-today-background: #D1FAE5;
--cal7-color-event-border: #A7F3D0;
}- Roles: dialog, menu, menuitem with aria‑expanded/aria‑controls bound to triggers
- Modal focus loop (dependency‑free) with Escape and outside‑click dismiss
- Keyboard navigation: arrows in menus, Tab cycle in modal
- Respects prefers‑reduced‑motion and high‑contrast
- 🚀 Zero Configuration: Just set your API key and go
- 📱 Responsive Design: Adapts from desktop 7-day view to mobile-friendly layout
- ♿ Accessibility First: Full keyboard navigation and screen reader support
- 🎨 Customizable Theming: Extensive theme system with CSS custom properties
- 🌙 Dark Mode: Built-in dark theme with system preference detection
- đź“… Add to Calendar: Device-aware calendar integration (Google, Apple, iCal)
- đź”’ Secure: Server-side API key handling, no client-side exposure
- ⚡ Performance: Built-in caching and optimized rendering
- 📦 Zero runtime dependencies by default; small bundle footprint
- 🎯 TypeScript: Full type safety and IntelliSense support
This package is designed specifically for React-based frameworks with the following support levels:
- Next.js 13+ (App Router) - Primary target with full server-side rendering support
- Next.js 12+ (Pages Router) - Full compatibility with getServerSideProps/getStaticProps
- Create React App / Vite - Client-side only, requires custom
fetcherprop for API calls - Remix - May work with adaptation, but not officially tested
- Vue/Nuxt, Svelte/SvelteKit, Angular - Different framework ecosystems, would need separate packages
Recommended: Use with Next.js for the best experience with server-side rendering and secure API key handling.
import { Calendar } from "cal7";
export default function EventsPage() {
return <Calendar locale="en-US" timeZone="America/New_York" />;
}import { Calendar, ThemeProvider } from "cal7";
const customTheme = {
colors: {
primary: "#3b82f6",
background: "#ffffff",
eventBackground: "#f8fafc",
},
typography: {
fontFamily: "Inter, sans-serif",
customFontUrl:
"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap",
},
};
export default function App() {
return (
<ThemeProvider config={{ theme: customTheme }}>
<Calendar />
</ThemeProvider>
);
}Cal7 provides extensive theming capabilities through CSS custom properties and a comprehensive theme system.
import { Calendar, ThemeProvider, type CalendarTheme } from "cal7";
const customTheme: CalendarTheme = {
colors: {
// Primary colors
primary: "#3b82f6",
secondary: "#6b7280",
background: "#ffffff",
surface: "#f9fafb",
// Text colors
text: "#111827",
textSecondary: "#374151",
textMuted: "#6b7280",
eventPastText: "#9ca3af", // Color for past events
// Border colors
border: "#e5e7eb",
borderLight: "#f3f4f6",
// State colors
today: "#92400e",
todayBackground: "#fef3c7",
focus: "#3b82f6",
hover: "#f3f4f6",
// Day alternating colors
dayEven: "#fafafa",
dayOdd: "#ffffff",
dayHover: "#f8fafc",
// Header alternating colors
headerEven: "#f3f4f6",
headerOdd: "#f9fafb",
// Event colors
eventBackground: "#ffffff",
eventBorder: "#e5e7eb",
eventText: "#111827",
},
typography: {
fontFamily: "Inter, -apple-system, BlinkMacSystemFont, sans-serif",
customFontUrl:
"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap",
fontSize: {
xs: "0.75rem",
sm: "0.875rem",
base: "1rem",
lg: "1.125rem",
xl: "1.25rem",
"2xl": "1.5rem",
},
fontWeight: {
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
},
},
spacing: {
xs: "0.25rem",
sm: "0.5rem",
md: "1rem",
lg: "1.5rem",
xl: "2rem",
"2xl": "3rem",
},
shadows: {
calendar:
"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
sm: "0 1px 2px rgba(0, 0, 0, 0.05)",
md: "0 2px 4px rgba(0, 0, 0, 0.1)",
lg: "0 4px 6px rgba(0, 0, 0, 0.1)",
xl: "0 10px 15px rgba(0, 0, 0, 0.1)",
},
event: {
height: "auto", // Customizable event section height
width: "100%", // Customizable event width
titleMinHeight: "2.5rem", // Minimum height for event titles (2 lines)
},
};
export default function App() {
return (
<ThemeProvider config={{ theme: customTheme }}>
<Calendar />
</ThemeProvider>
);
}import { ThemeProvider } from "cal7";
export default function App() {
return (
<ThemeProvider
config={{
mode: "dark", // 'light' | 'dark' | 'system'
theme: customLightTheme,
darkTheme: customDarkTheme,
}}
>
<Calendar />
</ThemeProvider>
);
}You can also customize the calendar using CSS custom properties:
:root {
/* Colors */
--cal7-color-primary: #3b82f6;
--cal7-color-background: #ffffff;
--cal7-color-surface: #f9fafb;
--cal7-color-text: #111827;
--cal7-color-border: #e5e7eb;
/* Day alternating colors */
--cal7-color-day-even: #fafafa;
--cal7-color-day-odd: #ffffff;
--cal7-color-day-hover: #f8fafc;
/* Header alternating colors */
--cal7-color-header-even: #f3f4f6;
--cal7-color-header-odd: #f9fafb;
/* Past events */
--cal7-color-event-past-text: #9ca3af;
/* Typography */
--cal7-font-family: "Inter", sans-serif;
--cal7-font-size-base: 1rem;
--cal7-font-weight-bold: 700;
/* Event customization */
--cal7-event-height: auto;
--cal7-event-width: 100%;
--cal7-event-title-min-height: 2.5rem;
/* Shadows */
--cal7-shadow-calendar: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
/* Spacing */
--cal7-spacing-sm: 0.5rem;
--cal7-spacing-md: 1rem;
--cal7-spacing-lg: 1.5rem;
}const themeWithCustomFont = {
typography: {
fontFamily: "Poppins, sans-serif",
customFontUrl:
"https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap",
},
};| Prop | Type | Default | Description |
|---|---|---|---|
locale |
string |
'en-US' |
Locale for date/time formatting |
timeZone |
string |
'UTC' |
Timezone for event display |
revalidate |
number |
300 |
Cache revalidation time in seconds |
className |
string |
'' |
Additional CSS classes |
theme |
CalendarTheme |
undefined |
Custom theme configuration |
darkTheme |
CalendarTheme |
undefined |
Custom dark theme configuration |
mode |
ThemeMode |
'light' |
Theme mode: 'light', 'dark', or 'system' |
classPrefix |
string |
'cal7' |
CSS class prefix for custom styling |
showSubscribeButton |
boolean |
false |
Show "Subscribe to Calendar!" button with .ics download |
fetcher |
() => Promise<Event[]> |
undefined |
Custom data fetcher function |
onError |
(error: Error) => void |
undefined |
Error handler callback |
| Prop | Type | Default | Description |
|---|---|---|---|
config.theme |
CalendarTheme |
defaultTheme |
Light theme configuration |
config.darkTheme |
CalendarTheme |
defaultDarkTheme |
Dark theme configuration |
config.mode |
'light' | 'dark' | 'system' |
'light' |
Theme mode |
config.classPrefix |
string |
'cal7' |
CSS class prefix |
- Drop Shadow: Entire calendar component has a subtle drop shadow
- Alternating Colors: Days and headers alternate background colors for better visual separation
- Smooth Animations: Hover effects with scale and smooth transitions
- Past Event Styling: Past events are automatically greyed out using theme-appropriate colors
- Clock Icons: Time displays include clock icons
- Start/End Times: Shows both start and end times (e.g., "8:00AM to 10:00PM")
- Bold Titles: Event titles are bold and have increased font size
- Minimum Height: Event titles have a minimum 2-line height by default
- Location Icons: SVG location icons instead of emoji pins
- Customizable Dimensions: Event height and width can be customized via theme
- Clickable Links: Email addresses and URLs in event descriptions are automatically clickable
- Map Integration: Event locations are clickable and open in device-appropriate map apps
- Add to Calendar: Event modal dates are clickable with add-to-calendar functionality
- Themed Buttons: Add-to-calendar buttons match the selected theme (no gradients)
When enabled with showSubscribeButton={true}, displays a prominent "Subscribe to Calendar!" button that provides:
- Full Calendar Subscription: Users can subscribe to the entire calendar feed via URL
- Download .ics File: Users can download all calendar events as a local .ics file
- Upward-Opening Menu: Dropdown menu opens upward to avoid being cut off
- Theme Integration: Button styling matches your current theme with orange gradient
- Desktop/Mobile Friendly: Works across all devices and calendar applications
- Current Week Indicator: Shows "Current Week" badge when viewing the current week
- Themed Borders: Themed border between week selector and calendar grid
- Date Format: Week selector shows dates in "Jul 20 - 26, 2025" format
- Button Layout: Navigation arrows positioned left and right of "Today" button
Set your Google Calendar API key as an environment variable:
# .env.local
GOOGLE_CALENDAR_API_KEY=your_api_key_hereCal7 is built with TypeScript and provides full type definitions:
import type { CalendarTheme, CalendarEvent, ThemeConfig } from "cal7";
const theme: CalendarTheme = {
colors: {
primary: "#3b82f6",
// ... other theme properties with full IntelliSense
},
};- Keyboard Navigation: Full keyboard support with arrow keys, Enter, Space, and Escape
- Screen Reader Support: Comprehensive ARIA labels and live region announcements
- Focus Management: Proper focus trapping in modals and logical tab order
- High Contrast: Supports high contrast mode preferences
- Reduced Motion: Respects user's reduced motion preferences
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
MIT