react-carousel-latest
Version:
A headless, accessible, tree-shakeable React carousel with a compound-component API (and a backwards-compatible CardSlider preset).
201 lines (176 loc) • 4.59 kB
CSS
/*
* react-carousel-latest — base styles.
*
* Import once at your app root: import "react-carousel-latest/styles.css";
*
* Everything is namespaced with `rc-` and driven by CSS custom properties, so
* you can theme without overriding rules. There is NO Tailwind dependency.
*/
.rc-root {
/* Theming knobs — override on .rc-root or any ancestor. */
--rc-slide-size: 100%;
--rc-slide-gap: 1rem;
--rc-duration: 350ms;
--rc-easing: cubic-bezier(0.22, 1, 0.36, 1);
--rc-button-bg: rgba(17, 24, 39, 0.65);
--rc-button-bg-hover: rgba(17, 24, 39, 0.85);
--rc-button-color: #fff;
--rc-button-size: 2.5rem;
--rc-accent: #111827;
--rc-dot-color: rgba(17, 24, 39, 0.22);
--rc-dot-active-color: var(--rc-accent);
position: relative;
outline: none;
}
.rc-root:focus-visible {
outline: 2px solid var(--rc-accent);
outline-offset: 2px;
}
/* Clipping window. */
.rc-viewport {
overflow: hidden;
width: 100%;
height: 100%;
}
/*
* Vertical carousels need a bounded height so the stacked slides can be
* clipped and translated. Override --rc-viewport-height to taste.
*/
.rc-viewport[data-orientation="vertical"] {
height: var(--rc-viewport-height, 360px);
}
/*
* The moving strip. The track box is viewport-wide, so a translate of one
* slide-size (+ gap) advances exactly one slide — whether --rc-slide-size is a
* percentage (multiple cards per view) or a fixed width.
*/
.rc-track {
display: flex;
gap: var(--rc-slide-gap);
will-change: transform;
transition: transform var(--rc-duration) var(--rc-easing);
transform: translate3d(
calc(
var(--rc-active-index, 0) * -1 *
(var(--rc-slide-size, 100%) + var(--rc-slide-gap)) + var(--rc-drag, 0px)
),
0,
0
);
}
.rc-track[data-orientation="vertical"] {
flex-direction: column;
/* Fill the bounded viewport so a translate of one slide-size advances one
slide on the vertical axis, mirroring the horizontal case. */
height: 100%;
transform: translate3d(
0,
calc(
var(--rc-active-index, 0) * -1 *
(var(--rc-slide-size, 100%) + var(--rc-slide-gap)) + var(--rc-drag, 0px)
),
0
);
}
/* No animation while the finger is down — the content must track the pointer. */
.rc-track[data-dragging] {
transition: none;
cursor: grabbing;
}
.rc-slide {
flex: 0 0 var(--rc-slide-size);
min-width: 0;
min-height: 0;
user-select: none;
}
/* ----------------------------- Buttons ----------------------------- */
/* Base pill — used by every control (prev/next/first/last/play-pause). */
.rc-button {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--rc-button-size);
height: var(--rc-button-size);
padding: 0;
font-size: calc(var(--rc-button-size) * 0.5);
color: var(--rc-button-color);
background: var(--rc-button-bg);
border: none;
border-radius: 9999px;
cursor: pointer;
transition: background-color 150ms ease, opacity 150ms ease, transform 150ms ease;
}
.rc-button:hover:not(:disabled) {
background: var(--rc-button-bg-hover);
}
.rc-button:active:not(:disabled) {
transform: scale(0.92);
}
.rc-button:disabled {
opacity: 0.35;
cursor: default;
}
.rc-button:focus-visible {
outline: 2px solid var(--rc-accent);
outline-offset: 2px;
}
/* Prev/next overlay the track edges. */
.rc-button--prev,
.rc-button--next {
position: absolute;
top: 50%;
z-index: 2;
transform: translateY(-50%);
}
.rc-button--prev:active:not(:disabled),
.rc-button--next:active:not(:disabled) {
transform: translateY(-50%) scale(0.92);
}
.rc-button--prev {
left: 0.5rem;
}
.rc-button--next {
right: 0.5rem;
}
.rc-button__icon {
display: block;
}
/* Optional controls row for Start / Play-Pause / End etc. */
.rc-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin-top: 0.75rem;
}
/* ----------------------------- Dots ----------------------------- */
.rc-dots {
display: flex;
gap: 0.5rem;
justify-content: center;
margin-top: 0.75rem;
}
.rc-dot {
width: 0.625rem;
height: 0.625rem;
padding: 0;
background: var(--rc-dot-color);
border: none;
border-radius: 9999px;
cursor: pointer;
transition: background-color 150ms ease, transform 150ms ease;
}
.rc-dot:focus-visible {
outline: 2px solid var(--rc-accent);
outline-offset: 2px;
}
.rc-dot--active {
background: var(--rc-dot-active-color);
transform: scale(1.25);
}
/* Respect users who prefer reduced motion. */
@media (prefers-reduced-motion: reduce) {
.rc-track {
transition-duration: 1ms;
}
}