FullCalendar

free

FullCalendar renders day, week, month, year, and agenda views over one event list, with built-in drag-to-reschedule and resize-to-adjust-duration.

dayjsmotion
Preview
Dark
August 2026
    Sun
    Mon
    Tue
    Wed
    Thu
    Fri
    Sat
    26
    27
    28
    29
    30
    31
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    1
    2
    3
    4
    5
    Investor Update00:00
    Sprint Planning09:00
    Design Review14:00
    Product Launch09:00
    import { useState } from 'react';
    import FullCalendar from '@/components/composites/FullCalendar';
    import type { FullCalendarEvent } from '@/components/composites/FullCalendar';
    
    const now = new Date();
    const day = (n: number, hour = 0, minute = 0) =>
      new Date(now.getFullYear(), now.getMonth(), n, hour, minute).toISOString();
    
    const initialEvents: FullCalendarEvent[] = [
      {
        id: 1,
        title: 'Sprint Planning',
        startDate: day(3, 9, 0),
        endDate: day(3, 10, 0),
        color: 'blue',
        description: 'Plan the next two-week sprint.',
        type: 'meeting',
      },
      {
        id: 2,
        title: 'Design Review',
        startDate: day(8, 14, 0),
        endDate: day(8, 15, 0),
        color: 'purple',
        description: 'Review the new onboarding flow.',
        type: 'review',
      },
      {
        id: 3,
        title: 'Investor Update',
        startDate: day(12),
        endDate: day(14),
        color: 'orange',
        description: 'Quarterly investor progress update.',
        type: 'deadline',
      },
      {
        id: 4,
        title: 'Product Launch',
        startDate: day(20, 9, 0),
        endDate: day(20, 9, 30),
        color: 'green',
        description: 'Ship the v2 release.',
        type: 'launch',
      },
    ];
    
    export default function UsageDemo() {
      const [events, setEvents] = useState(initialEvents);
    
      return (
        <div className="h-145 w-full">
          <FullCalendar
            fillHeight
            events={events}
            onChange={(updatedEvents) => setEvents(updatedEvents)}
          />
        </div>
      );
    }

    Installation

    Add this component with the NateUI CLI.

    npx nateui@latest add FullCalendar

    Examples

    Custom Header

    Preview
    Dark
    August 2026
      Sun
      Mon
      Tue
      Wed
      Thu
      Fri
      Sat
      26
      27
      28
      29
      30
      31
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      1
      2
      3
      4
      5
      Customer Call11:00
      Board Meeting13:00
      import { useState } from 'react';
      import dayjs from 'dayjs';
      import Button from '@/components/ui/Button';
      import Select from '@/components/ui/Select';
      import FullCalendar from '@/components/composites/FullCalendar';
      import { PiArrowLeft, PiArrowRight } from 'react-icons/pi'
      import type {
        CalendarView,
        FullCalendarEvent,
      } from '@/components/composites/FullCalendar';
      
      const now = new Date();
      const day = (n: number, hour = 0, minute = 0) =>
        new Date(now.getFullYear(), now.getMonth(), n, hour, minute).toISOString();
      
      const initialEvents: FullCalendarEvent[] = [
        {
          id: 1,
          title: 'Customer Call',
          startDate: day(6, 11, 0),
          endDate: day(6, 11, 30),
          color: 'blue',
          description: 'Renewal discussion.',
          type: 'meeting',
        },
        {
          id: 2,
          title: 'Board Meeting',
          startDate: day(16, 13, 0),
          endDate: day(16, 15, 0),
          color: 'purple',
          description: 'Monthly board update.',
          type: 'meeting',
        },
      ];
      
      const viewOptions: { label: string; value: CalendarView }[] = [
        { label: 'Day', value: 'day' },
        { label: 'Week', value: 'week' },
        { label: 'Month', value: 'month' },
        { label: 'Year', value: 'year' },
        { label: 'Agenda', value: 'agenda' },
      ];
      
      export default function CustomHeaderDemo() {
        const [events, setEvents] = useState(initialEvents);
      
        return (
          <div className="h-145 w-full">
            <FullCalendar
              fillHeight
              events={events}
              onChange={(updatedEvents) => setEvents(updatedEvents)}
              renderHeaderStart={({ selectedDate }) => (
                <h5>{dayjs(selectedDate).format('MMMM YYYY')}</h5>
              )}
              renderHeaderEnd={({
                handlePrevious,
                handleNext,
                setSelectedDate,
                view,
                setView,
              }) => (
                <div className="flex items-center gap-2">
                  <Button
                    icon={<PiArrowLeft />}
                    onClick={handlePrevious}
                    className="text-sm"
                  />
                  <Button onClick={() => setSelectedDate(new Date())}>Today</Button>
                  <Button
                    icon={<PiArrowRight />}
                    onClick={handleNext}
                    className="text-sm"
                  />
                  <Select
                    className="w-28"
                    options={viewOptions}
                    value={viewOptions.find((option) => option.value === view)}
                    onChange={(selected) => setView(selected.value)}
                  />
                </div>
              )}
            />
          </div>
        );
      }

      Resizable Duration

      Preview
      Dark
      August 2026
        30 Sun
        01:00
        02:00
        03:00
        04:00
        05:00
        06:00
        07:00
        08:00
        09:00
        10:00
        11:00
        12:00
        13:00
        14:00
        15:00
        16:00
        17:00
        18:00
        19:00
        20:00
        21:00
        22:00
        23:00

        Focus Block

        09:00 - 11:00

        Client Workshop

        14:00 - 15:30

        13:20
        import { useState } from 'react';
        import FullCalendar from '@/components/composites/FullCalendar';
        import type { FullCalendarEvent } from '@/components/composites/FullCalendar';
        
        const now = new Date();
        const day = (n: number, hour = 0, minute = 0) =>
          new Date(now.getFullYear(), now.getMonth(), n, hour, minute).toISOString();
        
        const initialEvents: FullCalendarEvent[] = [
          {
            id: 1,
            title: 'Focus Block',
            startDate: day(now.getDate(), 9, 0),
            endDate: day(now.getDate(), 11, 0),
            color: 'green',
            description: 'Deep work, no meetings.',
            type: 'focus',
          },
          {
            id: 2,
            title: 'Client Workshop',
            startDate: day(now.getDate(), 14, 0),
            endDate: day(now.getDate(), 15, 30),
            color: 'purple',
            description: 'Requirements workshop.',
            type: 'meeting',
          },
        ];
        
        export default function ResizableDurationDemo() {
          const [events, setEvents] = useState(initialEvents);
        
          return (
            <div className="h-145 w-full">
              <FullCalendar
                fillHeight
                view="day"
                events={events}
                onChange={(updatedEvents) => setEvents(updatedEvents)}
              />
            </div>
          );
        }

        Custom Day Sidebar

        Preview
        Dark
        August 2026
          30 Sun
          01:00
          02:00
          03:00
          04:00
          05:00
          06:00
          07:00
          08:00
          09:00
          10:00
          11:00
          12:00
          13:00
          14:00
          15:00
          16:00
          17:00
          18:00
          19:00
          20:00
          21:00
          22:00
          23:00

          Project Check-in

          12:49 - 13:49

          13:20
          import { useState } from 'react';
          import dayjs from 'dayjs';
          import Calendar from '@/components/ui/Calendar';
          import FullCalendar from '@/components/composites/FullCalendar';
          import type { FullCalendarEvent } from '@/components/composites/FullCalendar';
          
          const now = new Date();
          
          const initialEvents: FullCalendarEvent[] = [
            {
              id: 1,
              title: 'Project Check-in',
              startDate: new Date(now.getTime() - 30 * 60 * 1000).toISOString(),
              endDate: new Date(now.getTime() + 30 * 60 * 1000).toISOString(),
              color: 'blue',
              description: 'Review current milestones and blockers.',
              type: 'meeting',
            },
          ];
          
          export default function CustomDaySidebarDemo() {
            const [events, setEvents] = useState(initialEvents);
          
            return (
              <div className="h-145 w-full">
                <FullCalendar
                  fillHeight
                  view="day"
                  events={events}
                  onChange={(updatedEvents) => setEvents(updatedEvents)}
                  renderDayViewSidebar={({
                    selectedDate,
                    setSelectedDate,
                    currentEvents,
                  }) => (
                    <aside className="hidden w-80 shrink-0 flex-col border-l lg:flex">
                      <div className="border-b px-5 py-4">
                        <p className="text-sm text-muted-foreground">Selected date</p>
                        <p className="font-semibold">
                          {dayjs(selectedDate).format('dddd, MMMM D')}
                        </p>
                      </div>
          
                      <Calendar
                        className="mx-auto w-fit py-4"
                        value={selectedDate}
                        onChange={(date) => date && setSelectedDate(date)}
                      />
          
                      <div className="border-t px-5 py-4">
                        <p className="mb-3 font-semibold">Happening now</p>
                        {currentEvents.length > 0 ? (
                          <div className="space-y-3">
                            {currentEvents.map((event) => (
                              <div key={event.id} className="rounded-md bg-muted p-3">
                                <p className="font-medium">{event.title}</p>
                                <p className="text-sm text-muted-foreground">
                                  {dayjs(event.startDate).format('h:mm A')} -{' '}
                                  {dayjs(event.endDate).format('h:mm A')}
                                </p>
                              </div>
                            ))}
                          </div>
                        ) : (
                          <p className="text-sm text-muted-foreground">
                            No events are in progress.
                          </p>
                        )}
                      </div>
                    </aside>
                  )}
                />
              </div>
            );
          }

          Custom Event Render

          Preview
          Dark
          August 2026
            Sun
            Mon
            Tue
            Wed
            Thu
            Fri
            Sat
            26
            27
            28
            29
            30
            31
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            31
            1
            2
            3
            4
            5
            Team Sync
            Launch Day
            Renewal Due
            import { useState } from 'react';
            import type { ReactNode } from 'react';
            import { PiFlag, PiRocketLaunch, PiUsers } from 'react-icons/pi'
            import FullCalendar from '@/components/composites/FullCalendar';
            import type { FullCalendarEvent } from '@/components/composites/FullCalendar';
            
            const now = new Date();
            const day = (n: number, hour = 0, minute = 0) =>
              new Date(now.getFullYear(), now.getMonth(), n, hour, minute).toISOString();
            
            const initialEvents: FullCalendarEvent[] = [
              {
                id: 1,
                title: 'Team Sync',
                startDate: day(4, 10, 0),
                endDate: day(4, 10, 30),
                color: 'blue',
                description: 'Weekly team sync.',
                type: 'meeting',
              },
              {
                id: 2,
                title: 'Launch Day',
                startDate: day(22),
                endDate: day(22),
                color: 'green',
                description: 'Ship to production.',
                type: 'launch',
              },
              {
                id: 3,
                title: 'Renewal Due',
                startDate: day(27),
                endDate: day(27),
                color: 'orange',
                description: 'Contract renewal deadline.',
                type: 'deadline',
              },
            ];
            
            const typeIcon: Record<string, ReactNode> = {
              meeting: <PiUsers />,
              launch: <PiRocketLaunch />,
              deadline: <PiFlag />,
            };
            
            const typeBorder: Record<string, string> = {
              meeting: 'border-l-palette-blue',
              launch: 'border-l-palette-emerald',
              deadline: 'border-l-palette-orange',
            };
            
            export default function CustomEventRenderDemo() {
              const [events, setEvents] = useState(initialEvents);
            
              return (
                <div className="h-145 w-full">
                  <FullCalendar
                    fillHeight
                    events={events}
                    onChange={(updatedEvents) => setEvents(updatedEvents)}
                    renderEvent={({ event }) => ({
                      className: `bg-card border rounded-l-none border-l-4 ${
                        typeBorder[event.type] ?? 'border-l-muted-foreground'
                      }`,
                      content: (
                        <div className="flex items-center gap-2 truncate">
                          <span className="text-muted-foreground">
                            {typeIcon[event.type]}
                          </span>
                          <span className="truncate font-medium">{event.title}</span>
                        </div>
                      ),
                    })}
                  />
                </div>
              );
            }

            More Overflow

            Preview
            Dark
            August 2026
              Sun
              Mon
              Tue
              Wed
              Thu
              Fri
              Sat
              26
              27
              28
              29
              30
              31
              1
              2
              3
              4
              5
              6
              7
              8
              9
              10
              11
              12
              13
              14
              15
              16
              17
              18
              19
              20
              21
              22
              23
              24
              25
              26
              27
              28
              29
              30
              31
              1
              2
              3
              4
              5
              Interview 109:00
              Interview 210:00
              Interview 311:00
              Interview 412:00
              Interview 513:00
              import { useState } from 'react';
              import dayjs from 'dayjs';
              import FullCalendar from '@/components/composites/FullCalendar';
              import type { FullCalendarEvent } from '@/components/composites/FullCalendar';
              
              const now = new Date();
              const day = (n: number, hour = 0, minute = 0) =>
                new Date(now.getFullYear(), now.getMonth(), n, hour, minute).toISOString();
              
              const busyDay = 10;
              
              const initialEvents: FullCalendarEvent[] = Array.from(
                { length: 7 },
                (_, i) => ({
                  id: i + 1,
                  title: `Interview ${i + 1}`,
                  startDate: day(busyDay, 9 + i, 0),
                  endDate: day(busyDay, 9 + i, 45),
                  color: i % 2 === 0 ? 'blue' : 'purple',
                  description: 'Candidate interview slot.',
                  type: 'meeting',
                }),
              );
              
              const dotColor: Record<string, string> = {
                blue: 'bg-palette-blue',
                purple: 'bg-palette-purple',
              };
              
              export default function MoreOverflowDemo() {
                const [events, setEvents] = useState(initialEvents);
              
                return (
                  <div className="w-full h-145">
                    <FullCalendar
                      fillHeight
                      events={events}
                      onChange={(updatedEvents) => setEvents(updatedEvents)}
                      renderMoreContent={({ overflowedEvents }) => (
                        <div className="flex flex-col gap-1">
                          <p className="px-2 text-xs font-medium text-muted-foreground">
                            {overflowedEvents.length} more
                          </p>
                          {overflowedEvents.map((event) => (
                            <div
                              key={event.id}
                              className="flex items-center gap-2 rounded-md px-2 py-1.5 hover:bg-accent"
                            >
                              <span
                                className={`h-2 w-2 shrink-0 rounded-full ${
                                  dotColor[event.color] ?? 'bg-palette-gray'
                                }`}
                              />
                              <span className="flex-1 truncate text-sm font-medium">
                                {event.title}
                              </span>
                              <span className="text-xs text-muted-foreground">
                                {dayjs(event.startDate).format('h:mm A')}
                              </span>
                            </div>
                          ))}
                        </div>
                      )}
                    />
                  </div>
                );
              }

              Fill Height

              Preview
              Dark
              August 2026
                Sun
                Mon
                Tue
                Wed
                Thu
                Fri
                Sat
                26
                27
                28
                29
                30
                31
                1
                2
                3
                4
                5
                6
                7
                8
                9
                10
                11
                12
                13
                14
                15
                16
                17
                18
                19
                20
                21
                22
                23
                24
                25
                26
                27
                28
                29
                30
                31
                1
                2
                3
                4
                5
                Standup09:00
                Retro16:00
                import { useState } from 'react';
                import FullCalendar from '@/components/composites/FullCalendar';
                import type { FullCalendarEvent } from '@/components/composites/FullCalendar';
                
                const now = new Date();
                const day = (n: number, hour = 0, minute = 0) =>
                  new Date(now.getFullYear(), now.getMonth(), n, hour, minute).toISOString();
                
                const initialEvents: FullCalendarEvent[] = [
                  {
                    id: 1,
                    title: 'Standup',
                    startDate: day(6, 9, 0),
                    endDate: day(6, 9, 15),
                    color: 'blue',
                    description: 'Daily standup.',
                    type: 'meeting',
                  },
                  {
                    id: 2,
                    title: 'Retro',
                    startDate: day(14, 16, 0),
                    endDate: day(14, 17, 0),
                    color: 'purple',
                    description: 'Sprint retrospective.',
                    type: 'meeting',
                  },
                ];
                
                export default function FillHeightDemo() {
                  const [events, setEvents] = useState(initialEvents);
                
                  return (
                    <div className="h-145 w-full rounded-card border">
                      <FullCalendar
                        fillHeight
                        events={events}
                        onChange={(updatedEvents) => setEvents(updatedEvents)}
                      />
                    </div>
                  );
                }

                API

                FullCalendar

                PropDescriptionTypeDefault
                eventsEvents to render. color accepts 'blue' | 'green' | 'red' | 'yellow' | 'purple' | 'orange' | 'gray'; any other value falls back to gray.FullCalendarEvent[]
                viewActive view. The built-in header only switches between Month, Week, and Agenda; reach Day and Year through view directly or a custom renderHeaderEnd.CalendarView'month'
                fillHeightFills the parent's height instead of the view's own default height. The parent chain must be height-bounded (an explicit height somewhere above), or the calendar collapses.booleanfalse
                onCellClickFires when an empty cell or time slot is clicked.(date: Date) => void
                onEventClickFires when an event is clicked. Not called by the year view (its event bullets aren't clickable).(event: FullCalendarEvent) => void
                onChangeFires after an event is moved (drag) or resized, with the full updated list and the one event that changed.(events: FullCalendarEvent[], event: FullCalendarEvent) => void
                onMoreClickObserves activation of a month-view cell's "+N more" control alongside the built-in or customized overflow Popover. Only used by the month view.(overflowedEvents: FullCalendarEvent[], allEvents: FullCalendarEvent[]) => void
                renderMoreContentCustomizes or replaces the built-in overflow Popover body in the month view; it is not required to enable the "+N more" Popover.(props: MoreContentRenderProps) => ReactNode
                renderEventReplaces an event's rendered content and class name. Not called by the year view.(props: EventRenderProps) => EventRenderResult
                renderDayViewSidebarReplaces the day view's complete sidebar wrapper, including its width and responsive behavior. Receives the selected date, its setter, and currently active single-day events. Return null to hide the sidebar.(props: DayViewSidebarRenderProps) => ReactNode
                renderHeaderStartReplaces the header's left/start section.(payload: FullCalendarHeaderPayload) => ReactNode | string
                renderHeaderEndReplaces the header's right/end section.(payload: FullCalendarHeaderPayload) => ReactNode | string

                Drag-to-move and resize-to-adjust-duration are always active on single-day events in the month, week, and day views — there is no prop to disable them. Both report through onChange, so a parent that doesn't feed updated events back in will see the drag or resize visually revert.

                Types

                type CalendarView = 'day' | 'week' | 'month' | 'year' | 'agenda';
                 
                type FullCalendarEvent = {
                  id: number;
                  startDate: string;
                  endDate: string;
                  title: string;
                  color: string;
                  description: string;
                  type: string;
                  disabled?: boolean;
                };
                 
                type EventRenderProps = {
                  event: FullCalendarEvent;
                  view: CalendarView;
                };
                 
                type EventRenderResult = {
                  className?: string;
                  content: ReactNode;
                };
                 
                type MoreContentRenderProps = {
                  overflowedEvents: FullCalendarEvent[];
                  allEvents: FullCalendarEvent[];
                };
                 
                type DayViewSidebarRenderProps = {
                  selectedDate: Date;
                  setSelectedDate: (date: Date) => void;
                  currentEvents: FullCalendarEvent[];
                };
                 
                type FullCalendarHeaderPayload = {
                  handlePrevious: () => void;
                  handleNext: () => void;
                  selectedDate: Date;
                  view: CalendarView;
                  setView: (view: CalendarView) => void;
                  setSelectedDate: (date: Date) => void;
                };