01 How settings work
Everything on this page is changed in one of three places. Knowing which is which saves a lot of hunting.
| Where | What it controls | Looks like |
|---|---|---|
| Your CSS | Size, colours, caption typography and position | #hero { height: 70vh; } |
| The preset name | The optical character as a whole | data-refrakt="darkroom" |
data-rf-options |
Individual behaviours: autoplay, which controls show, how it moves | data-rf-options='{"nav": {"arrows": false}}' |
About that third one
The data-rf-options attribute holds settings written in JSON — a strict but simple format. Every recipe below hands you the complete, working attribute, so in practice you copy a line and change a number or swap true for false. You never have to compose one from scratch.
Three rules keep JSON out of trouble:
- The attribute is wrapped in single quotes; everything inside uses double quotes.
data-rf-options='{"hash": true}'. Swapping those around is the single most common mistake. - No comma after the last item.
{"a": 1, "b": 2}— not{"a": 1, "b": 2,}. trueandfalseand numbers are written bare, without quotes."arrows": false, not"arrows": "false".
You cannot break the slider from here. If the JSON is malformed, Refrakt logs one console message telling you exactly what is wrong and carries on with the preset. If a number is out of range it is pulled to the nearest allowed value; if a name is misspelled it is ignored — again, each with one plain-English console line. A mistake in this attribute changes how the slider looks. It never stops it working.
Long attributes are easier to read broken over several lines. HTML is fine with that:
<div id="hero"
data-refrakt="boardroom"
data-rf-options='{"motion": {"autoplay": {"enabled": true, "interval": 5000}},
"nav": {"counter": true}}'>
02 Size and shape
The slider fills whatever box you put it in — no settings involved, just CSS on your container. Nothing else on this page matters if the container has no height, so start here.
Height
| You want | Write |
|---|---|
| A fixed height | #hero { height: 600px; } |
| A proportion of the screen height | #hero { height: 70vh; } |
| A fixed shape, whatever the width | #hero { aspect-ratio: 16 / 9; } |
| Taller on phones, wider on desktop | See below |
#hero { height: 70vh; }
/* phones: taller, so portrait subjects aren't cropped to a letterbox */
@media (max-width: 700px) {
#hero { height: 78vh; }
}
Edge to edge, full screen
#hero {
height: 100svh; /* small-viewport height — correct under mobile browser bars */
margin: 0;
}
body { margin: 0; }
100svh rather than 100vh matters on phones: it measures the viewport with the browser's address bar showing, so the bottom of your slider is not hidden behind it.
Several sliders on one page? Give each container its own id and its own height. They run independently, can use different presets, and share a single canvas behind the scenes — see the vitrine demo.
03 Colours
Arrows, dots, the counter, the progress bar, focus outlines, the enlarge view's backdrop and your caption text all take their colour from exactly two CSS variables. Set those two and the whole slider is re-themed. There is no third one to learn.
#hero {
--rf-ink: #f5f2ea; /* text, arrows, dots, active states */
--rf-paper: #0c0c0e; /* control backgrounds, scrims, backdrops */
}
Think of it as writing with ink on paper. Dark ink on light paper for a bright page; light ink on dark paper for a dark one.
| Page style | Paste this |
|---|---|
| Light page — white or near-white background | --rf-ink: #111111; --rf-paper: #fafafa; |
| Dark page — black or near-black background | --rf-ink: #f5f2ea; --rf-paper: #0c0c0e; |
| Warm / editorial — cream paper, near-black ink | --rf-ink: #241a10; --rf-paper: #f4ede3; |
| An accent colour — a brand tint on a dark page | --rf-ink: #ff9d2e; --rf-paper: #000000; |
You can also write them straight onto the element, which is handy while you're experimenting:
<div id="hero" data-refrakt="eclipse"
style="--rf-ink:#ff9d2e; --rf-paper:#000000">
Keep the two far apart in lightness. If your ink and paper are similar — mid-grey on light grey, say — the arrows and dots become hard to see and the slider fails accessibility guidelines. Both pairs above measure better than 15:1; aim for at least 4.5:1 with your own.
Caption colour, separately
Captions inherit the ink colour, which is usually what you want. To colour them independently — white text over a dark photograph on an otherwise light page, for instance — style your own caption elements:
#hero .rf-text { color: #ffffff; }
If the photo behind the words is busy, add the shipped backing panel instead of guessing at a colour:
<figcaption class="rf-text rf-scrim">
<h2>Harbour, 5 a.m.</h2>
</figcaption>
rf-scrim lays your paper colour at 72% opacity behind the caption — enough to read through, not enough to hide the picture.
Caption position and typography
The caption is your HTML in a layer that tracks the slide, so it takes ordinary CSS. Nothing about it is locked:
#hero .rf-text {
position: absolute;
left: 5vw;
bottom: 8vh;
max-width: 22em;
}
#hero .rf-text h2 {
font-size: clamp(2rem, 5vw, 4rem);
line-height: 1.05;
letter-spacing: -0.03em;
margin: 0 0 0.4em;
}
Every demo does exactly this — open any demo's index.html and read its <style> block for a worked example you can lift.
04 Autoplay
Off by default. To turn it on:
<div id="hero" data-refrakt="boardroom"
data-rf-options='{"motion": {"autoplay": {"enabled": true}}}'>
To choose the pace, add an interval in milliseconds — 5000 is five seconds:
data-rf-options='{"motion": {"autoplay": {"enabled": true, "interval": 5000}}}'
| Setting | Default | Notes |
|---|---|---|
"enabled" | false | true starts it advancing on its own. |
"interval" | 6000 | Milliseconds per slide. Anything from 1500 to 60000. Below about 4000 is uncomfortable to read. |
"pauseOnHover" | true | Stops while the pointer is over the slider. Leave it on. |
Four things happen automatically once autoplay is on, and none of them are optional:
- A pause button appears. Anything that moves on its own for more than five seconds must be stoppable — that is a hard accessibility requirement, so the button cannot be switched off. Style it with
.rf-pause. The spacebar also pauses and resumes. - It stops when the tab is hidden, so a background tab is not quietly burning battery.
- It stops when the slider scrolls off screen, and resumes when it comes back. A slider halfway down a long page does not advance to slide 9 while nobody is looking.
- It stops while the enlarge view is open, so the picture someone is studying does not change under them.
Visitors who have asked their system for reduced motion still get the slides — autoplay keeps running — but the optical motion around them is stilled: no colour dispersion, no drifting light, no breathing. The slider stays useful without being distracting.
The boardroom preset already turns autoplay on at a slow 7-second pace. If you are using it and want a different rhythm, you only need the "interval" part.
05 Arrows, dots and counters
Five separate pieces of chrome, each independently on or off. All of them live under "nav".
| Piece | Setting | Default | What it is |
|---|---|---|---|
| Arrows | "arrows" | true | Previous / next buttons at the sides. |
| Dots | "spectrum" | true | One dot per slide; the active one condenses to solid ink. |
| Counter | "counter" | false | A fraction — 03 / 08. |
| Progress bar | "progressRule" | false | A hairline rule that fills as you move through. |
| Editorial menu | "editorialMenu" | false | Large numbered entries, labelled from each slide's first heading. Replaces dots on type-led designs. |
Recipes
| You want | Paste |
|---|---|
| Nothing but the picture — no controls at all | data-rf-options='{"nav": {"arrows": false, "spectrum": false}}' |
| Dots only, no arrows | data-rf-options='{"nav": {"arrows": false}}' |
| Arrows only, no dots | data-rf-options='{"nav": {"spectrum": false}}' |
| A counter and a progress bar instead of dots | data-rf-options='{"nav": {"spectrum": false, "counter": true, "progressRule": true}}' |
| The big numbered menu | data-rf-options='{"nav": {"spectrum": false, "editorialMenu": true}}' |
Hiding every control does not make the slider unusable: dragging, the mouse wheel, arrow keys and swipe all keep working, and screen-reader users still get slide announcements. But do give visitors some visible sign there is more than one slide.
Restyling rather than hiding? Each piece is an ordinary button with a stable class — .rf-arrow, .rf-dot, .rf-counter, .rf-rule, .rf-menu-item. Write normal CSS against them; the full list is in the reference.
06 How it moves
Under "motion". These change how the slider responds to a drag, a flick or a wheel.
| You want | Paste |
|---|---|
| Stop at the last slide instead of wrapping around | data-rf-options='{"layout": {"loop": false}}' |
| Let a hard flick travel several slides | data-rf-options='{"motion": {"maxStep": 4}}' |
| A longer, looser glide after release | data-rf-options='{"motion": {"inertia": 0.8}}' |
| A crisper, more immediate stop | data-rf-options='{"motion": {"inertia": 0.3, "snapStrength": 1}}' |
| Turn off the mouse wheel (it's inside a scrolling page) | data-rf-options='{"motion": {"wheel": false}}' |
| Start on a slide other than the first | data-rf-options='{"layout": {"startIndex": 2}}' — counting from 0, so this is the third slide |
| Slide vertically instead of horizontally | data-rf-options='{"layout": {"axis": "y"}}' |
Drag, wheel and keyboard can each be switched off with false — but leave "keyboard" alone. Arrow-key navigation is how keyboard and screen-reader users operate the slider at all.
07 Click to enlarge
Off by default. Turn it on and clicking the current slide opens it large, over the page:
data-rf-options='{"lightbox": {"enabled": true}}'
The opening animation is worth seeing: the glass distortion irons itself flat as the picture expands, so the enlarged image is the undistorted photograph. Escape closes it, and focus returns to where it was.
Video slides from YouTube or Vimeo always open this way regardless of the setting — the video has nowhere else to play.
08 Making slides clickable
Put the link in the caption. It stays a real link — visitors can middle-click it, copy the address, and reach it by keyboard, none of which is true of a slide-sized click handler:
<figure class="rf-slide">
<img src="img/autumn.jpg" alt="A wool coat in raw sienna">
<figcaption class="rf-text">
<h2>Autumn collection</h2>
<p><a class="cta" href="/collections/autumn">Shop the collection</a></p>
</figcaption>
</figure>
Style it into a button with your own CSS:
#hero .cta {
display: inline-block;
padding: 0.7em 1.4em;
border: 1px solid currentColor;
border-radius: 100px;
text-decoration: none;
font-weight: 600;
}
Dragging the slider will not fire the link by accident — a drag and a click are told apart before either happens.
Want the whole slide to be a link? Wrap the caption content in one large anchor and give it a size in CSS. Avoid making the image itself a link: it sits behind the glass and is not the element visitors actually click.
09 Video and YouTube
A video slide is the same <figure> with one extra attribute. No settings, no separate mode.
Your own video file
<figure class="rf-slide"
data-rf-video="video/atelier-loop.mp4"
data-rf-poster="img/atelier-poster.jpg">
</figure>
The video plays inside the glass — it refracts and disperses exactly as a photograph does. The poster is the still frame shown until it is ready; always supply one.
YouTube or Vimeo
<figure class="rf-slide"
data-rf-youtube="dQw4w9WgXcQ"
data-rf-poster="img/talk-poster.jpg">
</figure>
<figure class="rf-slide"
data-rf-vimeo="76979871"
data-rf-poster="img/film-poster.jpg">
</figure>
Use the bare video ID, not the full web address. The poster refracts in the slider like any other image, and clicking plays the video full-size in the enlarge view.
Always supply data-rf-poster for YouTube and Vimeo slides. With a poster, your page makes no request to Google or Vimeo until a visitor actually chooses to play — which keeps the page fast and keeps third-party tracking off it. It also means the slide looks right on the first frame instead of popping in.
Formats, file sizes, autoplay rules on phones and everything else about media: the Media Guide.
10 Linking to one slide
Turn on hash and the address bar tracks the current slide, so yoursite.com/#hero=3 opens directly on slide 3 — shareable, bookmarkable, and the back button steps through the slides:
<div id="hero" data-refrakt="atelier" data-rf-options='{"hash": true}'>
The container's id is what appears in the address, so give it a short, meaningful one. This is the only feature that requires an id.
11 Dialling the glass up or down
A preset is close but too strong, or not strong enough? You do not have to abandon it — override the one number you care about and everything else in the preset stays.
| You want | Paste |
|---|---|
| Calmer glass — less bending | data-rf-options='{"glass": {"ior": 0.05}}' (0 is flat, 0.35 is maximum) |
| Less colour fringing on drag | data-rf-options='{"dispersion": {"amount": 0.1}}' (0–1) |
| No tearing at all, however hard you flick | data-rf-options='{"dispersion": {"maxTear": 0}}' |
| Finer, more numerous ribs | data-rf-options='{"glass": {"reeded": {"frequency": 30}}}' (4–96) |
| Move the light source | data-rf-options='{"light": {"x": 0.2, "y": 0.1}}' (0,0 is top-left; 1,1 bottom-right) |
| More film grain | data-rf-options='{"exposure": {"grain": 0.2}}' (0–0.5) |
| No glass at all — a clean, fast slider | data-rf-options='{"glass": {"kind": "none"}}' |
Don't guess at numbers — drag them. Open the optics-lab demo, move the sliders until the glass looks the way you want, and the panel gives you the exact line to copy. It is much faster than editing and reloading.
Every one of these is one row of a table in the Options reference, where you will find its exact range and what it means physically. Twelve finished combinations — condensation wiped clear by the pointer, a museum vitrine, cinematic bloom — are in the Optics Cookbook.
12 Putting several together
Recipes combine by nesting them under the same top-level names. Two autoplay settings and two nav settings share one "motion" and one "nav":
<div id="hero"
data-refrakt="boardroom"
data-rf-options='{"motion": {"autoplay": {"enabled": true, "interval": 5000}},
"nav": {"arrows": true, "spectrum": false, "counter": true},
"lightbox": {"enabled": true},
"hash": true}'>
Written out, that is: the boardroom look, advancing every five seconds, with arrows and a counter but no dots, clicking to enlarge, and the address bar tracking the slide.
Anything you don't mention keeps whatever the preset chose, and anything the preset doesn't mention keeps the factory default. You are always adjusting, never starting from nothing — which is why the recipes on this page are safe to paste one at a time.
Repeating a top-level name in the same attribute silently discards the first one — {"nav": {"arrows": false}, "nav": {"counter": true}} gives you the counter and keeps the arrows. Merge them into a single "nav" block instead.