How to Make a “Scroll to Select” Form Control
The element is a fairly straightforward concept: focus on it to reveal a set of
s that can be selected as the input’s value. That’s a great pattern and I’m not suggesting we change it. That said, I do enjoy poking at things and found an interesting way to turn a
into a dial of sorts — where options are selected by scrolling them into position, not totally unlike a combination lock or iOS date pickers. Anyone who’s expanded a
for selecting a country knows how painfully long lists can be and this could be one way to prevent that.
Here’s what I’m talking about:
It’s fairly common knowledge that styling in CSS is not the easiest thing in the world. But here’s the trick: we’re not working with at all. No, we’re not going to do anything like building our own by jamming a bunch of JavaScript into a
<section class=scroll-container>
<label for="madrid" class="scroll-item">
Madrid
<abbr>MAD</abbr>
<input id="madrid" type="radio" name="items">
</label>
<label for="malta" class="scroll-item">
Malta
<abbr>MLA</abbr>
<input id="malta" type="radio" name="items">
</label>
<!-- etc. -->
</section>
What we need is to style the list of selectable controls where we are capable of managing their sizes and spacing in CSS. I’ve gone with a group of labels with nested radio boxes as far as the markup goes. The exact styling is totally up to you, of course, but you can use these base styles I wrote up if you want a starting point.
.scroll-container {
/* SIZING & LAYOUT */
--itemHeight: 60px;
--itemGap: 10px;
--containerHeight: calc((var(--itemHeight) * 7) + (var(--itemGap) * 6));
width: 400px;
height: var(--containerHeight);
align-items: center;
row-gap: var(--itemGap);
border-radius: 4px;
/* PAINT */
--topBit: calc((var(--containerHeight) - var(--itemHeight))/2);
--footBit: calc((var(--containerHeight) + var(--itemHeight))/2);
background: linear-gradient(
rgb(254 251 240),
rgb(254 251 240) var(--topBit),
rgb(229 50 34 / .5) var(--topBit),
rgb(229 50 34 / .5) var(--footBit),
rgb(254 251 240)
var(--footBit));
box-shadow: 0 0 10px #eee;
}
A couple of details on this:
-
--itemHeight
is the height of each item in the list. -
--itemGap
is meant to be the space between two items. - The
--containerHeight
variable is the .scroll-container’s height. It’s the sum of the item sizes and the gaps between them, ensuring that we display, at maximum, seven items at once. (An odd number of items gives us a nice balance where the selected item is directly in the vertical center of the list). - The background is a striped gradient that highlights the middle area, i.e., the location of the currently selected item.
- The
--topBit
and –-footBit
variables are color stops that visually paint in the middle area (which is orange in the demo) to represent the currently selected item.
I’ll arrange the controls in a vertical column with flexbox declared on the .scroll-container:
.scroll-container {
display: flex;
flex-direction: column;
/* rest of styles */
}
With layout work done, we can focus on the scrolling part of this. If you haven’t worked with CSS Scroll Snapping before, it’s a convenient way to direct a container’s scrolling behavior. For example, we can tell the .scroll-container
that we want to enable scrolling in the vertical direction. That way, it’s possible to scroll to the rest of the items that are not in view.
.scroll-container {
overflow-y: scroll;
/* rest of styles */
}
Next, we reach for the scroll-snap-style
property that can be used to tell the .scroll-container
that we want scrolling to stop on an item — not near an item, but directly on it.
.scroll-container {
overflow-y: scroll;
scroll-snap-type: y mandatory;
/* rest of styles */
}
Now items “snap” onto an item instead of allowing a scroll to end wherever it wants. One more little detail I like to include is overscroll-behavior
, specifically along the y-axis as far as this demo goes:
.scroll-container {
overflow-y: scroll;
scroll-snap-type: y mandatory;
overscroll-behavior-y: none;
/* rest of styles */
}
overscroll-behavior-y: none
isn’t required to make this work, but when someone scrolls through the .scroll-container
(along the y-axis), scrolling stops once the boundary is reached, and any further continued scrolling action will not trigger scrolling in any nearby scroll containers. Just a form of defensive CSS.
Time to move to the items inside the scroll container. But before we go there, here are some base styles for the items themselves that you can use as a starting point:
.scroll-item {
/* SIZING & LAYOUT */
width: 90%;
box-sizing: border-box;
padding-inline: 20px;
border-radius: inherit;
/* PAINT & FONT */
background: linear-gradient(to right, rgb(242 194 66), rgb(235 122 51));
box-shadow: 0 0 4px rgb(235 122 51);
font: 16pt/var(--itemHeight) system-ui;
color: #fff;
input { appearance: none; }
abbr { float: right; } /* The airport code */
}
As I mentioned earlier, the --itemHeight
variable is setting as the size of each item and we’re declaring it on the flex
property — flex: 0 0 var(--itemHeight)
. Margin is added before and after the first and last items, respectively, so that every item can reach the middle of the container through scrolling.
The scroll-snap-align
property is there to give the .scroll-container
a snap point for the items. A center alignment, for instance, snaps an item’s center (vertical center, in this case) with the .scroll-container
‘s center (vertical center as well). Since the items are meant to be selected through scrolling alone pointer-events: none
is added to prevent selection from clicks.
One last little styling detail is to set a new background on an item when it is in a :checked
state:
.scroll-item {
/* Same styles as before */
/* If input="radio" is :checked */
&:has(:checked) {
background: rgb(229 50 34);
}
}
But wait! You’re probably wondering how in the world an item can be :checked
when we’re removing pointer-events
. Good question! We’re all finished with styling, so let’s move on to figuring some way to “select” an item purely through scrolling. In other words, whatever item scrolls into view and “snaps” into the container’s vertical center needs to behave like a typical form control selection. Yes, we’ll need JavaScript for that.
let observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
with(entry) if(isIntersecting) target.children[1].checked = true;
});
}, {
root: document.querySelector(`.scroll-container`), rootMargin: `-51% 0px -49% 0px`
});
document.querySelectorAll(`.scroll-item`).forEach(item => observer.observe(item));
The IntersectionObserver
object is used to monitor (or “observe”) if and when an element (called a target
) crosses through (or “intersects”) another element. That other element could be the viewport itself, but in this case, we’re observing the .scroll-container
for when a .scroll-item
intersects it. We’ve established the observed boundary with rootMargin:"-51% 0px -49% 0px"
.
A callback function is executed when that happens, and we can use that to apply changes to the target element, which is the currently selected .scroll-item
. In our case, we want to select a .scroll-item
that is at the halfway mark in the .scroll-container
: target.children[1].checked = true
.
That completes the code. Now, as we scroll through the items, whichever one snaps into the center position is the selected item. Here’s a look at the final demo again:
Let’s say that, instead of selecting an item that snaps into the .scroll-container
‘s vertical center, the selection point we need to watch is the top of the container. No worries! All we do is update the scroll-snap-align
property value from center to start in the CSS and remove the :first-of-type
‘s top margin. From there, it’s only a matter of updating the scroll container’s background gradient so that the color stops highlight the top instead of the center. Like this:
And if one of the items has to be pre-selected when the page loads, we can get its position in JavaScript (getBoundingClientRect()
) and use the scrollTo()
method to scroll the container to where that specific item’s position is at the point of selection (which we’ll say is the center in keeping with our original demo). We’ll append a .selecte
d class on that .scroll-item
.
<section class="scroll-container">
<!-- more items -->
<label class="scroll-items selected">
2024
<input type=radio name=items />
</label>
<!-- more items -->
</section>
Let’s select the .selected
class, get its dimensions, and automatically scroll to it on page load:
let selected_item = (document.querySelector(".selected")).getBoundingClientRect();
let scroll_container = document.querySelector(".scroll-container");
scroll_container.scrollTo(0, selected_item.top - scroll_container.offsetHeight - selected_item.height);
It’s a little tough to demo this in a typical CodePen embed, so here’s a live demo in a GitHub Page (source code). I’ll drop a video in as well:
That’s it! You can build up this control or use it as a starting point to experiment with different layouts, styles, animations, and such. It’s important the UX clearly conveys to the users how the selection is done and which item is currently selected. And if I was doing this in a production environment, I’d want to make sure there’s a good fallback experience for when JavaScript might be unavailable and that my markup performs well on a screen reader.
References and further reading
- A Few Functional Uses for Intersection Observer to Know When an Element is in View (Preethi Sam)
- An Explanation of How the Intersection Observer Watches (Travis Almand)
- Practical CSS Scroll Snapping (Max Kohler)
- The Current State of Styling Selects in 2019 (Chris Coyier)
- CSS Flexbox Layout Guide (CSS-Tricks)
- CSS flex property (CSS-Tricks)
- CSS Scroll Snap Properties (MDN)
- scrollTo() (MDN)
How to Make a “Scroll to Select” Form Control originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
3 Essential Design Trends, October 2024
You have to get creative when you lack a strong visual. This month’s design trends roundup focuses on this concept.
Quick Hit #21
Seeing a lot more headlines decrying JavaScript and pumping up PHP. Always interesting to see which direction the front-end zeitgeist is leaning.
Quick Hit #21 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
CSSWG Minutes Telecon (2024-09-18)
For the past two months, all my livelihood has gone towards reading, researching, understanding, writing, and editing about Anchor Positioning, and with many Almanac entries published and a full Guide guide on the way, I thought I was ready to tie a bow on it all and call it done. I know that Anchor Positioning is still new and settling in. The speed at which it’s moved, though, is amazing. And there’s more and more coming from the CSSWG!
That all said, I was perusing the last CSSWG minutes telecon and knew I was in for more Anchor Positioning when I came to the following resolution:
Whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree) (Issue #10526: When does
anchor-scope
“match” a name?)
Resolutions aren’t part of the specification or anything, but the strongest of indications about where they’re headed. So, I thought this was a good opportunity not only to take a peek at what we might get in anchor-scope
and touch on other interesting bits from the telecon.
Remember that you can subscribe and read the full minutes on W3C.org. 🙂
What’s anchor-scope
?
To register an anchor, we can give it a distinctive anchor-name
and then absolutely positioned elements with a matching position-anchor
are attached to it. Even though it may look like it, anchor-name
doesn’t have to be unique — we may reuse an anchor element inside a component with the same anchor-name
.
<ul>
<li>
<div class="anchor">Anchor 1</div>
<div class="target">Target 1</div>
</li>
<li>
<div class="anchor">Anchor 2</div>
<div class="target">Target 2</div>
</li>
<li>
<div class="anchor">Anchor 3</div>
<div class="target">Target 3</div>
</li>
</ul>
However, if we try to connect them with CSS,
.anchor {
anchor-name: --my-anchor;
}
.target {
position: absolute;
position-anchor: --my-anchor;
position-area: top right;
}
We get an unpleasant surprise where instead of each .anchor
have their .target
positioned at its top-right edge, meaning they all pile up on the last .anchor
instance. We can see it better by rotating each target a little. You’ll want to check out the next demo in Chrome 125+ to see the behavior:
The anchor-scope
property should make an anchor element only discoverable by targets in their individual subtree. So, the prior example would be fixed in the future like this:
.anchor {
anchor-name: --my-anchor;
anchor-scope: --my-anchor;
}
This is fairly straightforward — anchor-scope
makes the anchor element available only in that specific subtree. But then we have to ask another question: What should the anchor-scope
own scope be? We can’t have an anchor-scope-scope
property and then an anchor-scope-scope-scope
and so on… so which behavior should it be?
This is what started the conversation, initially from a GitHub issue:
When an
anchor-scope
is specified with a, it scopes the name to that subtree when the anchor name is “matching”. The problem is that this matching can be interpreted in at least three ways: (Assuming that
anchor-scope
is a tree-scoped reference, which is also not clear in the spec):
- It matches by the ident part of the name only, ignoring any tree-scope that would be associated with the name, or
- It matches by exact match of the ident part and the associated tree-scope, or
- It matches by some mechanism similar to dereferencing of tree-scoped references, where it’s a match when the tree-scope of the anchor-scope-name is an inclusive ancestor of the tree-scope of the anchor query.
And then onto the CSSWG Minutes:
TabAtkins: In anchor positioning, anchor names and references are tree scoped. The
anchor-scope
property that scopes, does not say whether the names are tree scoped or not. Question to decide: should they be?TabAtkins: I think the answer should be yes. If you have an anchor in a shadow tree with a part involved, then problems result if anchor scopes are not tree scoped. This is bad, so I think it should be tree scoped sounds pretty reasonable makes sense to me as far as I can understand it 🙂
This solution of the scope of scoping properties expanded towards View Transitions, which also rely on tree scoping to work:
khush: Thinking about this in the context of view transitions: in that API you give names and the tree scope has to be the same for them to match. There is another view transitions feature where I’m not sure if the spec says it’s tree scoped
khush: Want to make sure that feature is covered by the more general resolution
TabAtkins: Proposed more general resolution: whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree)
So the scope of anchor-scope
is tree-scoped. Say that five times fast!
RESOLVED: whenever you are comparing names, and at least one is tree scoped, then both are tree scoped, and the scoping has to be exact (not subtree)
The next resolution was pretty straightforward. Besides allowing a , the
anchor-scope
property can take an all
keyword, which means that all anchors are tree-scoped, while the says that specific anchor is three-scoped. So, the question was if
all
is also a tree-scoped value.
TabAtkins:
anchor-scope
, in addition to idents, can take the keyword ‘all
‘, which scopes all names. Should this be a tree-scoped ‘all
‘? (i.e. only applies to the current tree scope)TabAtkins: Proposed resolution: the ‘
all
‘ keyword is also tree-scoped in the same way sgtm +1, again same pattern withview-transition-group
RESOLVED: the ‘
all
‘ keyword is tree-scoped
The conversation switched gears toward new properties coming in the CSS Scroll Snap Module Level 2 draft, which is all about changing the user’s initial scroll with CSS. Taking anexample directly from the spec, say we have an image carousel:
<div class="carousel">
<img src="img1.jpg">
<img src="img2.jpg">
<img src="img3.jpg" class="origin">
<img src="img4.jpg">
<img src="img5.jpg">
</div>
We could start our scroll to show another image by setting it’s scroll-start-targe
to auto
:
.carousel {
overflow-inline: auto;
}
.carousel .origin {
scroll-start-target: auto;
}
As of right now, the only way to complete this is using JavaScript to scroll an element into view:
document.querySelector(".origin").scrollIntoView({
behavior: "auto",
block: "center",
inline: "center"
});
The last example is probably a carousel that is only scrollable in the inline direction. Still, there are doubts as far when the container is scrollable in both the inline and block directions. As seen in the initial GitHub issue:
The scroll snap 2 spec says that when there are multiple elements that could be
scroll-start-targets
for a scroll container “user-agents should select the one which comes first in tree order“.Selecting the first element in tree-order seems like a natural way to resolve competition between multiple targets which would be scrolled to in one particular axis but is perhaps not as flexible as might be needed for the 2d case where an author wants to scroll to one item in one axis and another item in the other axis.
And back to the CSSWG minutes:
DavidA: We have a property we’re adding called
scroll-start-target
that indicates if an element within a scroll container, then the scroll should start with that element onscreen. Question is what happens if there are multiple targets?
DavidA: Propose to do it in reverse-DOM order, this would result in the first one applied last and then be on screen. Also, should only change the scroll position if you have to.
After discussing why we have to define scroll-start-target
when we have scroll-snap-align
, the discussion went on discuss the reverse-DOM order:
fantasai: There was a bunch of discussion about regular vs reverse-DOM order. Where did we end up and why?
flackr: Currently, we expect that it scrolls to the first item in DOM order. We probably want that to still happen. That is why the proposal is to scroll to each item in sequence in reverse-DOM order.
So we are coming in reverse to scroll the element, but only as required so the following elements are showing as much as possible:
flackr: There is also the issue of nearest…
fantasai: Can you explain nearest?
flackr: Same as scroll into view
fantasai: ?
flackr: This is needed with you scroll multiple things into view and want to find a good position (?)
fantasai: You scroll in reverse-DOM order…when you add the spec can you make it really clear that this is the end result of the algorithm?
flackr: Yes absolutely
fantasai: Otherwise it seems to make sense
And so it was resolved:
Proposed resolution 2: When
scroll-start-target
targets multiple elements, scroll to each in reverse DOM order with text to specify priority is the first item
Lastly, there was the debate about the text-underline-position
, that when set to auto
says, “The user agent may use any algorithm to determine the underline’s position; however it must be placed at or under the alphabetic baseline.” The discussion was about whether the auto
value should automatically adjust the underlined position to match specific language rules, for example, at the right of the text for vertical writing modes, like Japanese and Mongolian.
fantasai: The initial value of
text-underline-position
isauto
, which is defined as “find a good place to put the underline”.
Three options there: (1) under alphabetical baseline, (2) fully below text (good for lots-of-descenders cases), (3) for vertical text on the RHS
fantasai: auto value is defined in the spec about ‘how far down below the text’, but doesn’t say things about flipping. The current spec says “at or below”. In order to handle language-specific aspects, there is a default UA style sheet that for Chinese and Japanese and Korean there are differences for those languages. A couple of implementations do this
fantasai: Should we change the spec to mention these things?
fantasai: Or should we stick with the UA stylesheet approach?
The thing is that Chrome and Firefox already place the underline on the right in vertical Japanese when text-underline-position
is auto
.
The group was left wiuth three options:
A) Keep spec as-is, update Gecko + Blink to match (using UA stylesheet for language switch)
B) Introduce auto totext-emphasis-positio
n and use it in bothtext-emphasis-position
andtext-underline-position
to effect language switches
C) Adopt inconsistent behavior:text-underline-position
uses ‘auto
‘ andtext-emphasis-position
uses UA stylesheetMany CSSWG members like Emilio Cobos, TabAtkins, Miriam Suzanne, Rachel Andrew and fantasai casted their votes, resulting in the following resolution:
RESOLVED: add auto value for text-emphasis-position, and change the meaning of text-underline-position: auto to care about left vs right in vertical text
I definitely encourage you to read at the full minutes! Or if you don’t have the time, you can there’s a list just of resolutions.
CSSWG Minutes Telecon (2024-09-18) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Quick Hit #20
Having fun with Bramus’ new Caniuse CLI tool. This’ll save lots of trips to the Caniuse site!
Quick Hit #20 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Quick Hit #19
Two possible syntaxes for CSS masonry, one draft specification, and you get to share your opinions.
Quick Hit #19 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
A Beginner’s Guide to Using BlueSky for Business Success
In today’s fast-paced digital world, businesses are always on the lookout for new ways to connect with their audience. BlueSky, a decentralized social media platform, is quickly gaining attention as a fresh alternative to traditional platforms like Twitter and Instagram. While it’s still early days for BlueSky, its unique structure offers a big opportunity for businesses to build communities and engage with users in a more transparent, user-focused way.
Clever Polypane Debugging Features I’m Loving
I’m working on a refresh of my personal website, what I’m calling the HD remaster. Well, I wouldn’t call it a “full” redesign. I’m just cleaning things up, and Polypane is coming in clutch. I wrote about how much I enjoy developing with Polypane on my personal blog back in March 2023. In there, I say that I discover new things every time I open the browser up and I’m here to say that is still happening as of August 2024.
Polypane, in case you’re unfamiliar with it, is a web browser specifically created to help developers in all sorts of different ways. The most obvious feature is the multiple panes displaying your project in various viewport sizes:
I’m not about to try to list every feature available in Polypane; I’ll leave that to friend and creator, Kilian Valkhof. Instead, I want to talk about a neat feature that I discovered recently.
Outline tab
Inside Polypane’s sidebar, you will find various tabs that provide different bits of information about your site. For example, if you are wondering how your social media previews will look for your latest blog post, Polypane has you covered in the Meta tab.

The tab I want to focus on though, is the Outline tab. On the surface, it seems rather straightforward, Polypane scans the page and provides you outlines for headings, landmarks, links, images, focus order, and even the full page accessibility tree.

Seeing your page this way helps you spot some pretty obvious mistakes, but Polypane doesn’t stop there. Checking the Show issues option will point out some of the not-so-obvious problems.

In the Landmarks view, there is an option to Show potentials as well, which displays elements that could potentially be page landmarks.

In these outline views, you also can show an overlay on the page and highlight where things are located.

Now, the reason I even stumbled upon these features within the Outline tab is due to a bug I was tracking down, one specifically related to focus order. So, I swapped over to the “Focus order” outline to inspect things further.

That’s when I noticed the option to see an overlay for the focus order.

This provides a literal map of the focus order of your page. I found this to be incredibly useful while troubleshooting the bug, as well as a great way to visualize how someone might navigate your website using a keyboard.
These types of seemingly small, but useful features are abundant throughout Polypane.
Amazing tool
When I reached out to Kilian, mentioning my discovery, his response was “Everything’s there when you need it!”
I can vouch for that.
Clever Polypane Debugging Features I’m Loving originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Multiple Anchors
Only Chris, right? You’ll want to view this in a Chromium browser:
This is exactly the sort of thing I love, not for its practicality (cuz it ain’t), but for how it illustrates a concept. Generally, tutorials and demos try to follow the “rules” — whatever those may be — yet breaking them helps you understand how a certain thing works. This is one of those.
The concept is pretty straightforward: one target element can be attached to multiple anchors on the page.
<div class="anchor-1"></div>
<div class="anchor-2"></div>
<div class="target"></div>
We’ve gotta register the anchors and attach the .target
to them:
.anchor-1 {
anchor-name: --anchor-1;
}
.anchor-2 {
anchor-name: --anchor-2;
}
.target {
}
Wait, wait! I didn’t attach the .target
to the anchors. That’s because we have two ways to do it. One is using the position-anchor
property.
.target {
position-anchor: --anchor-1;
}
That establishes a target-anchor relationship between the two elements. But it only accepts a single anchor value. Hmm. We need more than that. That’s what the anchor()
function can do. Well, it doesn’t take multiple values, but we can declare it multiple times on different inset properties, each referencing a different anchor.
.target {
top: anchor(--anchor-1, bottom);
}
The second piece of anchor()
‘s function is the anchor edge we’re positioned to and it’s gotta be some sort of physical or logical inset — top
, bottom
, start
, end
, inside
, outside
, etc. — or percentage. We’re bascially saying, “Take that .target
and slap it’s top
edge against --anchor-1
‘s bottom edge.
That also works for other inset properties:
.target {
top: anchor(--anchor-1 bottom);
left: anchor(--anchor-1 right);
bottom: anchor(--anchor-2 top);
right: anchor(--anchor-2 left);
}
Notice how both anchors are declared on different properties by way of anchor()
. That’s rad. But we aren’t actually anchored yet because the .target
is just like any other element that participates in the normal document flow. We have to yank it out with absolute positioning for the inset properties to take hold.
.target {
position: absolute;
top: anchor(--anchor-1 bottom);
left: anchor(--anchor-1 right);
bottom: anchor(--anchor-2 top);
right: anchor(--anchor-2 left);
}
In his demo, Chris cleverly attaches the .target
to two elements. What makes it clever is that
allows you to click and drag it to change its dimensions. The two of them are absolutely positioned, one pinned to the viewport’s top-left edge and one pinned to the bottom-right.
If we attach the .target's top
and left
edges to --anchor-1
‘s bottom
and right
edges, then attach the target's bottom
and right
edges to --anchor-2
‘s top
and left
edges, we’re effectively anchored to the two elements. This is what allows the
.target
element to stretch with the elements when they are resized.
But there’s a small catch: a is resized from its bottom-right corner. The second
is positioned in a way where the resizer isn’t directly attached to the
.target
. If we rotate(180deg)
, though, it’s all good.
Again, you’ll want to view that in a Chromium browser at the time I’m writing this. Here’s a clip instead if you prefer.
That’s just a background-color
on the .target
element. We can put a little character in there instead as a background-image
like Chris did to polish this off.
Fun, right?! It still blows my mind this is all happening in CSS. It wasn’t many days ago that something like this would’ve been a job for JavaScript.
Multiple Anchors originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
Smashing Hour With Lynn Fisher
I’m a big Lynn Fisher fan. You probably are, too, if you’re reading this. Or maybe you’re reading her name for the first time, in which case you’re in for a treat.
That’s because I had a chance to sit down with Lynn for a whopping hour to do nothing more than gab, gab, and gab some more. I love these little Smashing Hours because they’re informal like that and I feel like I really get to know the person I’m talking with. And this was my very first time talking with Lynn, we had a ton to talk about — her CSS art, her annual site refreshes, where she finds inspiration for her work, when the web started to “click” for her… and so much more.
Don’t miss the bit where Lynn discusses her current site design (~24 min.) because it’s a masterclass in animation and creativity.
Smashing Hour With Lynn Fisher originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.