Source Code

<div class="container py-5">
  <div class="card bcal-card mx-auto">
    <div class="card-body p-4">
      <div class="d-flex justify-content-between align-items-center mb-3">
        <button class="btn btn-sm btn-outline-secondary" id="bcalPrev">&laquo; Prev</button>
        <h5 class="fw-bold mb-0" id="bcalMonthLabel">Month Year</h5>
        <button class="btn btn-sm btn-outline-secondary" id="bcalNext">Next &raquo;</button>
      </div>

      <div class="row row-cols-7 g-1 text-center small text-muted fw-semibold mb-1">
        <div class="col">Sun</div><div class="col">Mon</div><div class="col">Tue</div><div class="col">Wed</div><div class="col">Thu</div><div class="col">Fri</div><div class="col">Sat</div>
      </div>
      <div class="row row-cols-7 g-1" id="bcalGrid"></div>

      <hr>
      <h6 class="fw-bold mb-2" id="bcalEventsLabel">Select a date</h6>
      <ul class="list-group list-group-flush" id="bcalEventsList"></ul>
    </div>
  </div>
</div>

Bootstrap Calendar Event Widget — Free Snippet

Bootstrap Calendar Event Widget · Dashboards · Plain HTML, CSS & JS · Live preview

What's included

Features

Month grid generated entirely from JS Date math, no hardcoded day-of-week or days-in-month tables
Correctly handles 28/29/30/31-day months including leap-year February
Leading and trailing days from adjacent months shown muted, rounding the grid to full weeks
Prev/Next buttons regenerate the entire grid with correct month/year rollover at year boundaries
Events keyed by a plain YYYY-M-D string rather than Date object identity for reliable lookups
Real DOM dot indicators on dates with events, rebuilt correctly on every re-render
Clicking a date reveals that day's events in a Bootstrap list-group-flush below the grid
Today's date visually outlined independent of whether it is currently selected

About this UI Snippet

Bootstrap Calendar Event Widget — HTML, CSS & JavaScript

Screenshot of the Bootstrap Calendar Event Widget snippet rendered live

Building a correct month grid is mostly a date-math problem, and this snippet solves it the same way a real calendar library would: by asking JavaScript's own Date object for the answers instead of hardcoding a 7x5 layout. new Date(viewYear, viewMonth, 1).getDay() gives the weekday the month starts on (its offset into the first week), and new Date(viewYear, viewMonth + 1, 0).getDate() — the zero-th day of next month — gives the exact number of days in the current month, correctly handling 28, 29, 30, and 31-day months (including leap-year February) with no lookup table.

The grid is built from a single render() function that computes totalCells as the offset plus the days in month, rounded up to the next multiple of 7 with Math.ceil(... / 7) * 7, guaranteeing a clean rectangular grid every month regardless of how many weeks it spans. For each cell index, a small piece of arithmetic decides whether it falls before the 1st (rendered as a muted trailing day from the previous month, using daysInPrevMonth), after the last day (a muted leading day from next month), or inside the current month — and normalizes month/year rollover with ((cellMonth % 12) + 12) % 12 so that December of one year correctly wraps to January of the next without producing a negative month index, which is the kind of off-by-one that breaks naive calendar implementations at year boundaries.

Events are stored in a plain EVENTS object keyed by a "YYYY-M-D" string built via addEvent(), deliberately not a Date object, since Date equality by reference makes objects a poor map key. Any in-month day whose key has entries gets a small orange .bcal-dot appended as a child element — a real DOM node, not a background-image trick — so it survives the grid being rebuilt on every render. Clicking a day sets selectedKey, re-runs render() so the selection highlight (.bcal-selected) repaints correctly, and calls renderEventsFor() to populate a Bootstrap list-group-flush beneath the grid with that day's events, or a muted "No events for this day" message when the list is empty.

Prev/Next navigation simply decrements or increments viewMonth and lets the same normalization logic used for date math roll viewYear when viewMonth goes below 0 or above 11, then calls render() again — the entire grid, including which cells are muted and which carry event dots, is recomputed from scratch on every navigation rather than incrementally patched, which keeps the logic simple and correct at the cost of a full re-render (cheap for a 42-cell grid).

Because render() and renderEventsFor() never hold state outside two plain variables (viewYear, viewMonth) and a selection key, the whole widget maps cleanly onto React useState, Vue refs, or Angular component properties, with the render function becoming a derived JSX/template expression instead of imperative innerHTML writes.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI coding assistant like Claude to add a "Today" button that jumps the grid back to the current month, or to support multi-day events that render a dot on every day they span. It's also worth asking it to add keyboard arrow-key navigation between dates for accessibility.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a Bootstrap 5.3 calendar event widget using the real Bootstrap CDN (bootstrap.min.css and bootstrap.bundle.min.js), not custom CSS made to resemble Bootstrap.

Requirements:
- A real month grid computed from JavaScript Date math: correctly determine the weekday the 1st of the month falls on and the number of days in the month (handling all month lengths and leap years), and pad the grid with muted leading/trailing days from adjacent months so it always fills complete weeks.
- Prev and Next buttons that regenerate the entire grid for the adjacent month, correctly rolling the year over at the December/January boundary in both directions.
- Some dates should carry a small dot indicator marking they have events, using a data structure keyed by year-month-day rather than Date object identity.
- Clicking a date highlights it as selected and reveals that specific day's events in a list below the calendar, or a "no events" message if it has none.
- Today's date must be visually distinguished (e.g. an outline) independent of whether it is the currently selected date.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Load the snippetA full month grid for the current month appears, correctly aligned so the 1st falls under its real weekday, with today's date outlined and a couple of days showing a small orange event dot.
  2. 2
    Click a date with a dotThe events list below the calendar updates to show that day's specific events, and the clicked date is highlighted dark.
  3. 3
    Click a date without a dotThe events list updates to show "No events for this day" instead of leaving the previous selection's events visible.
  4. 4
    Click NextThe grid regenerates for the following month, correctly recalculating which weekday the 1st falls on and how many days the new month has.
  5. 5
    Click Prev repeatedly past JanuaryThe year label decrements and the month wraps to December, proving the month/year rollover math handles year boundaries correctly.

Real-world uses

Common Use Cases

DASHBOARD
Team scheduling dashboards
Drop this next to an admin dashboard sidebar as a compact team calendar widget.
Booking and appointment apps
Use the date-click-to-reveal pattern as the base for a slot picker, similar in interaction spirit to a table row selection list.
Learning calendar date math
A clear, dependency-free reference for computing day offsets, days-in-month, and month/year rollover with the native Date object.
Project and sprint planning views
Pair with a kanban board to show sprint milestones on a calendar alongside the task board.
CHAT
Content and editorial calendars
Repurpose the event dots to mark scheduled posts, useful next to a blog post card grid for a publishing dashboard.

Got questions?

Frequently Asked Questions

It asks new Date(viewYear, viewMonth + 1, 0).getDate() — day zero of next month, which JavaScript resolves to the last day of the current month — so 28, 29, 30, and 31-day months, including leap years, are all handled correctly with no manual lookup table.

viewMonth is decremented to -1, which the code detects and corrects by setting viewMonth to 11 (December) and decrementing viewYear by one, so navigation correctly crosses the year boundary backward.

Two different Date objects representing the same calendar day are not === equal in JavaScript, which makes Date a poor object key; a plain "year-month-day" string like "2026-8-15" is stable and reliable to look up in the EVENTS object.

Yes — store viewYear/viewMonth/selectedKey in useState or a ref, compute the grid cells in a useMemo or computed property using the same Date-based offset and days-in-month formulas, and render event dots conditionally in JSX/template syntax instead of appending DOM nodes manually.

Yes — the card, row-cols-7, and list-group classes are purely presentational; the date math, event storage, and click handling are independent of styling and keep working if you swap in Tailwind utility classes on the same structure.

Call addEvent(year, month, day, "Event text") for each event before render() runs — month is zero-indexed (0 for January) to match JavaScript's Date convention, which is a common source of off-by-one bugs if forgotten.