Datepicker

Form element that allows users to select a date from a calendar interface.

Class name Type
.prs-date Component For <input>
.prs-cal Component Container
.prs-cal-header Part Container
.prs-cal-prev Part For <button>
.prs-cal-next Part For <button>
.prs-cal-my Part For current m/y
.prs-cal-days Part Day list
.prs-cal-week Part Weekday list
.prs-cal-day Part For <button>
.prs-cal-day_today State Today
.prs-cal-day_selected State Selected day
  • Default

    Browser datepicker for best compatibility, locale, and accessibility. Another benefit is that you can use this within an iframe and the popover will appear outside the overflow.

    <div class="w-96 max-w-xs"> <!-- <- this is just for demo purposes -->
      <label class="prs-form-control">
        <div class-"prs-label">
          <span class="prs-label-text">Date:</span>
        </div>
        <input type="date" class="prs-input prs-date" onfocus="this.showPicker()" />
      </label>
    </div>
    
  • Cally Example

    3rd party web component that can work in any framework and has great accessibility support. This example also uses Alpine.js for even easier implemention.

    <div x-data="{ date: null, open: false, }" class="w-96 max-w-xs"> <!-- <- this is just for demo purposes -->
    
      <button
        @click.prevent="open = !open"
        x-ref="cally"
        x-text="date || 'Pick a Date'"
        class="prs-input prs-date"
      ></button>
      <div
        @click.outside="open = false"
        @keydown.escape.window="open = false"
        x-anchor.bottom-start.offset.5="$refs.cally"
        x-show="open"
        x-transition
      >
        <calendar-date
          @change="date = $el.value; open = false"
          first-day-of-week="0"
          show-outside-days="true"
          format-weekday="short"
          locale="en-US"
          class="prs-cal"
        >
          <svg aria-label="Previous" class="w-4 h-4" slot="previous" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><use href="/_assets/prs-icons.svg#caret-left" /></svg>
          <svg aria-label="Next" class="w-4 h-4" slot="next" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><use href="/_assets/prs-icons.svg#caret-right" /></svg>
          <calendar-month></calendar-month>
        </calendar-date>
      </div>
    
    </div> <!-- <- this is just for demo purposes -->
    <script type="module" src="https://unpkg.com/cally"></script> <!-- <- this is just for demo purposes -->
    
  • Calendar

    If you are writing your own calendar, you can use this as a starting point. However, be aware that the accessibility requirements are extensive.

    <div class="w-96 max-w-[270px]"> <!-- <- this is just for demo purposes -->
      <div class="prs-cal">
        <div class="prs-cal-header">
          <button class="prs-cal-prev" aria-label="Previous month">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" role="img">
              <use href="/_assets/prs-icons.svg#caret-left" />
            </svg>
          </button>
          <button class="prs-cal-next" aria-label="Next month">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" role="img">
              <use href="/_assets/prs-icons.svg#caret-right" />
            </svg>
          </button>
          <span class="prs-cal-my">Month 20XX</span>
        </div>
        <div class="prs-cal-days">
          <div class="prs-cal-week">
            <span>Sun</span>
            <span>Mon</span>
            <span>Tue</span>
            <span>Wed</span>
            <span>Thu</span>
            <span>Fri</span>
            <span>Sat</span>
          </div>
          <button class="prs-cal-day prs-cal-day_disabled" aria-label="Mm DD, YYYY">26</button>
          <button class="prs-cal-day prs-cal-day_disabled" aria-label="Mm DD, YYYY">27</button>
          <button class="prs-cal-day prs-cal-day_disabled" aria-label="Mm DD, YYYY">28</button>
          <button class="prs-cal-day prs-cal-day_disabled" aria-label="Mm DD, YYYY">29</button>
          <button class="prs-cal-day prs-cal-day_disabled" aria-label="Mm DD, YYYY">30</button>
          <button class="prs-cal-day prs-cal-day_disabled" aria-label="Mm DD, YYYY">31</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">1</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">2</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">3</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">4</button>
          <button class="prs-cal-day prs-cal-day_selected" aria-label="Mm DD, YYYY selected">5</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">6</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">7</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">8</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">9</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">10</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">11</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">12</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">13</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">14</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">15</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">16</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">17</button>
          <button class="prs-cal-day prs-cal-day_today" aria-current="date" aria-label="Mm DD, YYYY">18</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">19</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">20</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">21</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">22</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">23</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">24</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">25</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">26</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">27</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">28</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">29</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">30</button>
          <button class="prs-cal-day" aria-label="Mm DD, YYYY">31</button>
          <button class="prs-cal-day prs-cal-day_disabled" aria-label="Mm DD, YYYY">1</button>
          <button class="prs-cal-day prs-cal-day_disabled" aria-label="Mm DD, YYYY">2</button>
          <button class="prs-cal-day prs-cal-day_disabled" aria-label="Mm DD, YYYY">3</button>
          <button class="prs-cal-day prs-cal-day_disabled" aria-label="Mm DD, YYYY">4</button>
          <button class="prs-cal-day prs-cal-day_disabled" aria-label="Mm DD, YYYY">5</button>
        </div>
      </div>
    </div>
    

CSS

Full Library

Component Only

.prs-date {}
.prs-cal {
  width: var(--prs-cal-width);
  background-color: var(--prs-c-white);
  box-shadow: inset 0 0 0 1px var(--prs-c-gray-300), 0 1px 3px rgb(0 0 0 / 0.2);
  border-radius: var(--prs-cal-radius);
}
.prs-cal calendar-month {
  padding: 0 0.5rem 0.5rem;
  gap: 0;
}
.prs-cal-header {
  padding: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.prs-cal::part(container) {
  gap: 0;
}
.prs-cal::part(header) {
  padding: 0.5rem 0.5rem 0;
}
.prs-cal::part(heading) {
  font-weight: normal;
  font-size: 16px;
}
.prs-cal-prev,.prs-cal-next,.prs-cal::part(previous),.prs-cal::part(next) {
  padding: 0;
  border: 0 none;
  width: 2.125rem;
  height: 2.125rem;
  background: transparent;
  flex-shrink: 0;
  flex-grow: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  border-radius: calc(var(--prs-cal-radius) * 2);
  transition-property: var(--prs-transition-property);
  transition-timing-function: var(--prs-transition-timing);
  transition-duration: var(--prs-transition-duration);
}
.prs-cal-prev:hover,.prs-cal-next:hover,
.prs-cal::part(previous):hover,.prs-cal::part(next):hover {
  background-color: var(--prs-c-gray-100);
  color: var(--prs-c-gray-900);
}
.prs-cal-next {
  order: 3;
}
.prs-cal-week {
  display: flex;
}
.prs-cal ::part(heading) {
  color: var(--prs-c-gray-600);
  text-align: center;
  font-size: 0.875rem;
}
.prs-cal-week > span,.prs-cal ::part(th) {
  width: 2.125rem;
  height: 2.125rem;
  color: var(--prs-c-gray-600) !important;
  text-align: center;
  font-weight: normal;
  font-size: 0.75rem !important;
  line-height: 1rem !important;
  user-select: none;
  pointer-events: none;
}
.prs-cal-days {
  padding: 0.5rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: start;
}
.prs-cal-day,.prs-cal ::part(day) {
  --cal-bdr: transparent;
  --cal-shd: transparent;
  appearance: none;
  border: 1px solid var(--cal-bdr);
  width: 2.125rem;
  height: 2.125rem;
  color: var(--prs-c-primary);
  font-size: 0.9375rem;
  font-family: var(--prs-ff-sans);
  font-variant-numeric: tabular-nums;
  line-height: normal;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  cursor: pointer;
  border-radius: calc(var(--prs-cal-radius) * 2);
  box-shadow: inset 0 0 0 1px var(--cal-shd);
  transition-property: var(--prs-transition-property);
  transition-timing-function: var(--prs-transition-timing);
  transition-duration: var(--prs-transition-duration);
}
.prs-cal ::part(day) {
  display: table-cell;
}
.prs-cal-day:hover,.prs-cal-day_hover,.prs-cal ::part(day):hover {
  background-color: var(--prs-c-primary-100);
}
.prs-cal-day:focus-visible,.prs-cal-day_focus,.prs-cal ::part(day):focus-visible {
  --cal-bdr: currentColor;
  background: transparent;
  outline: none;
}
.prs-cal-day_selected,.prs-cal ::part(selected) {
  --cal-bdr: var(--prs-c-white);
  --cal-shd: var(--prs-c-white);
  background-color: var(--prs-c-primary);
  color: var(--prs-c-white);
}
.prs-cal-day_selected:hover,.prs-cal ::part(selected):hover {
  --cal-bdr: var(--prs-c-primary-100);
  --cal-shd: var(--prs-c-primary-100);
  background-color: var(--prs-c-primary-600);
}
.prs-cal-day_selected:focus,.prs-cal ::part(selected):focus-visible {
  --cal-bdr: var(--prs-c-primary);
  --cal-shd: var(--prs-c-white);
  background-color: var(--prs-c-primary);
}
.prs-cal-day_today:after,.prs-cal ::part(today):after {
  width: 0.1875rem;
  height: 0.1875rem;
  background-color: currentColor;
  border-radius: var(--prs-radius-badge);
  position: absolute;
  left: 50%;
  bottom: 0.1875rem;
  transform: translateX(-50%);
  content: '';
}
.prs-cal-day_disabled {
  color: var(--prs-c-gray);
}

Coming soon...