A calendar with a start date picked: hovering a later day previews the whole range, with a tinted band that runs from the start circle to the day under the pointer. Built with ~ and :has().
Also known as: date range hover preview css, calendar hover effect, range picker highlight.
October 2026
<div class="hfx-date-range-hover">
<p>October 2026</p>
<div class="hfx-date-range-hover-week" aria-hidden="true"><span>M</span><span>T</span><span>W</span><span>T</span><span>F</span><span>S</span><span>S</span></div>
<div class="hfx-date-range-hover-grid">
<button>5</button><button>6</button><button class="hfx-date-range-hover-start" aria-pressed="true">7</button><button>8</button><button>9</button><button>10</button><button>11</button>
<button>12</button><button>13</button><button>14</button><button>15</button><button>16</button><button>17</button><button>18</button>
<button>19</button><button>20</button><button>21</button><button>22</button><button>23</button><button>24</button><button>25</button>
</div>
</div>.hfx-date-range-hover {
--c: var(--hfx-color, #3b82f6);
font-size: var(--hfx-size, 16px);
font-family: system-ui, sans-serif;
}
.hfx-date-range-hover p {
margin: 0 0 0.4em 0.6em;
font-weight: 700;
}
.hfx-date-range-hover-week,
.hfx-date-range-hover-grid {
display: grid;
grid-template-columns: repeat(7, 2.3em);
}
.hfx-date-range-hover-week span {
padding-bottom: 0.4em;
color: color-mix(in srgb, currentColor 50%, transparent);
font-size: 0.72em;
font-weight: 600;
text-align: center;
}
.hfx-date-range-hover-grid { row-gap: 0.25em; }
.hfx-date-range-hover-grid button {
position: relative;
isolation: isolate;
height: 2.1em;
padding: 0;
border: 0;
background: none;
color: inherit;
font: inherit;
font-variant-numeric: tabular-nums;
cursor: pointer;
transition: color 0.2s ease;
}
/* the day circle */
.hfx-date-range-hover-grid button::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
z-index: -1;
width: 1.95em;
height: 1.95em;
margin: -0.975em 0 0 -0.975em;
border-radius: 50%;
background: color-mix(in srgb, var(--c) 18%, transparent);
transform: scale(0);
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), background-color 0.2s ease;
}
/* the range band */
.hfx-date-range-hover-grid button::after {
content: "";
position: absolute;
inset: 0.08em 0;
z-index: -2;
background: color-mix(in srgb, var(--c) 14%, transparent);
transform: scaleX(0);
transition: transform 0.25s ease;
}
.hfx-date-range-hover-grid button:is(:hover, :focus-visible)::before { transform: scale(1); }
/* the chosen start date */
.hfx-date-range-hover-grid .hfx-date-range-hover-start {
color: #fff;
font-weight: 700;
}
.hfx-date-range-hover-grid .hfx-date-range-hover-start::before {
background: var(--c);
transform: scale(1);
}
/* days between the start and the hovered day */
.hfx-date-range-hover-start ~ button:has(~ button:is(:hover, :focus-visible))::after { transform: scaleX(1); }
/* the hovered day becomes the end cap */
.hfx-date-range-hover-start ~ button:is(:hover, :focus-visible) {
color: #fff;
font-weight: 700;
}
.hfx-date-range-hover-start ~ button:is(:hover, :focus-visible)::before { background: var(--c); }
.hfx-date-range-hover-start ~ button:is(:hover, :focus-visible)::after {
transform: scaleX(0.5);
transform-origin: left;
}
.hfx-date-range-hover-grid:has(.hfx-date-range-hover-start ~ button:is(:hover, :focus-visible)) .hfx-date-range-hover-start::after {
transform: scaleX(0.5);
transform-origin: right;
}
@media (prefers-reduced-motion: reduce) {
.hfx-date-range-hover, .hfx-date-range-hover *, .hfx-date-range-hover::before, .hfx-date-range-hover::after, .hfx-date-range-hover *::before, .hfx-date-range-hover *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--hfx-color on the element or a parent.
Currently previewing #3b82f6.--hfx-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.