https://utilitybend.com/blog/the-customizable-select-part-four-scroll-snapping-state-queries-monster-hunter-and-gamification
Manage Cookies
This website uses cookies to provide a better experience, as well as
to analyze the usage of the website. We don't use marketing cookies,
but only google analytics to get to know our audience
[ ] Necessary
[ ] Analytics
Necessary only Accept all
Skip to content
Return to homepage
Open menu
Close menu
* home
* blog
* about
* contact
Current theme: light (click to switch) Toggle mode
1. Home
2. Blog
3. The customizable select - Part four: Scroll snapping, state
queries, monster hunter, and gamification
The customizable select - Part four: Scroll snapping, state queries,
monster hunter, and gamification
20 Jun 2025
[visual]
In this (long) part of the customizable select series, it's all about
gamification. In this article, I'd like to highlight one of my demos,
where I aimed to recreate a piece of UI found in the Monster Hunter
games. To re-create this behavior, I had to think in terms of
keyboard navigation first. This demo requires quite a lot of CSS, as
well as some scripting, and in the end, I do want to highlight some
accessibility concerns. This is an experiment on how far we can take
it when styling select elements.
This is a long one... and I still wasn't able to cover everything. So I
can understand if you want to jump to some sections.
* Setting up the HTML
* CSS setup
* Adding the gamification with JavaScript
* Accessibility: context is everything here
* Conclusion
For those that just want to play around with it, here is that final
result already
See the Pen Monster Hunter World item wheel with customizable select
by utilitybend (@utilitybend) on CodePen.
The idea: Monster Hunter games
I am a big fan of the Monster Hunter games, with "Monster Hunter
Wilds" being the last in the series. I started this demo while
waiting for that last entry to be released, so visually it has more
of a Monster Hunter Wold vibe (the previous version). If you are
clueless about what I'm going on about, here is a video that shows
the UI in the game:
I also added a little screenshot for easy reference:
A select in a game where the item in che center is the item selected,
there are icons of a d-pad on each sides howing which button to press
to scroll left and right, 5 items are visible, the center item is the
selected one
Where we left off...
In the previous parts, we've covered basic styling, radial
positioning, and sticky options. This time, we're going horizontal
and focusing on keystrokes and making it draggable. It's a different
idea, but I think the result turned out really cool. It's quite a
long demo so I won't be going over every border or style in detail.
Instead, I'd like to highlight a few parts on how this actually
works.
Setting up our HTML
Let's start with the structure. It's a bit more complex than our
previous demos because we need scroll arrows and an extra frame, more
about those later on.
The options
The options consist out of a few elements that we're going to
position differently. Each of the options will hold an icon with the
image of an item, a .title containing a name and an .amount
containing how many items are left of the type.
The extra elements
There are few extra divs to found here, more info on them below but
for a fast read, here is a run down of why certain elements are
added:
* The .frame is used because we want the items to be visually
dragged behind the frame, the item in the frame is the focussed
item which will be selected
* The extra .items element is what we'll need to set up our
scroll-snapping
* The arrows are for single pointer modality
We are taking things a bit further here with the .frame element and
the scroll arrows. Notice that I'm adding the type="button" on the
scroll arrows as well as I don't want the opt-in to think of this as
a trigger button. The arrows are added because I wanted to make this
accessible as possible by at least offering a single pointer
modality. More on the accessibility part of things later on because
it does raise a few questions.
Basic CSS setup and not so progressive enhancement...
For this demo I didn't really go the full progressive enhancement
route. Don't get me wrong, it's still a fully functional select, it's
just not styled because I thought the CSS was already getting
complicated enough. I still added a feature query tho.
First of all, let's set some variables we'll be using throughout this
demo. They are pretty much self-explanatory, just your colors,
borders, and sizes.
:root {
--base-icon-size: 64px;
--icon-size-wrap: 50px;
--btn-bg: rgba(70 70 70 / 0.9);
--border-color: #282929;
--border-width: 4px;
}
Sizes to note:
* The --base-icon-size will be the size of an option
* The --icon-size-wrap will be the actual size of an icon.
Next up, it's time to set our opt-in. The following part of the CSS
setup will be wrapped in a feature query:
@supports (appearance: base-select) {
select,
::picker(select) {
appearance: base-select;
}
/* Next parts in here */
}
Ok, let's start with some of that basic select styling:
select {
position: relative;
display: flex;
padding: 0;
justify-content: center;
anchor-name: --my-select;
background: none;
border: none;
&::picker-icon {
display: none;
}
}
A few things are going on here. The first thing to notice is that the
select is relatively positioned. The reason for this is that we'll be
placing some pseudo-elements on it later on. However, compared to my
other demos, where I position the picker on top of the select, for
this one, I am setting my custom anchor-name to the select element.
We will be hanging a lot more items on the select, such as the .title
and .amount, which is why we need that bit of extra control.
We're also hiding the ::picker-icon again.
The borders and selectedcontent on open state
In the example UI, we see a bunch of borders. To create this kind of
behavior, I wanted to add those to the open state of the select. Feel
free to visit the example in detail to get the full styling of it.
Another important thing that we wanted to do is that we don't see the
SVG in our when the select is :open, so we'll make
that one invisible as well.
select {
/* previous styles */
&:open {
selectedcontent svg {
opacity: 0;
}
/* The horizontal lines in our options */
&::before,
&::after {
/* Border styles and positioning */
}
}
}
Creating the frame
This was the trickiest part to figure out. I wanted items to scroll
under a frame, like they're behind glass. The solution was to create
two identical-looking elements, one of those will be our button
holding the content, which I gave the .trigger class for
convenience, the other one is the dedicated .frame element:
.trigger,
.frame {
inline-size: var(--base-icon-size);
aspect-ratio: 1;
border: var(--border-width) solid var(--border-color);
border-radius: 5px;
&::before,
&::after {
position: absolute;
inset-block-start: 40%;
inset-inline: -17px auto;
inline-size: 30px;
aspect-ratio: 1;
background: var(--border-color);
clip-path: polygon(50% 0%, 80% 50%, 50% 100%, 20% 50%);
content: "";
}
&::after {
inset-inline: auto -17px;
}
}
But, there is more, we're going full anchoring in this demo by
setting an anchor-name for the trigger button (--button) and for the
frame (--frame). While also providing a bit of positioning. We also
want a visible overflow on the trigger because we're going to
position some of the text in our outside of our