⚓️ What is Astro and What is Scroll to Anchor? #
In this post we will see how to build Astro Scroll to Anchor functionality into your static site. Before we get on to that though, we should take a quick peek at what Astro and scroll to anchor are. Astro is a new static site builder which lets you build fast websites. The secret to its speed is something called partial hydration which means you, as a developer, get more control over when JavaScript on a page loads. You can even ship zero JavaScript when none is needed. Scroll to anchor is a nice feature we have come to expect on websites where a little link icon appears if you hover over a heading. You can click the link to scroll smoothly to that heading, as well as even copy and save it for future reference.
🧱 What we’re Building #
![Astro Scroll to Anchor: Image is zoomed in on a paragraph of text with a heading above. Following the heading text is a link icon.](https://rodneylab-nebula-a-1zqt.shuttle.app/v1/lab/post/astro-scroll-to-anchor/astro-scroll-to-anchor-building.69186c1d9223.png?w=592&h=192&fit=min&auto=format)
Having said that Astro's superpower is partial hydration, we're going to build out the scroll functionality with no hydration at all. Meaning no JavaScript is required for our page; we will add the smooth scrolling and link auto show/hide using CSS. Although you can use Astro with Lit, React, Svelte or Vue, we will create a pure Astro component to add the feature. This will make it easier for you to recycle the code for use in your own Astro project using your preferred framework. Have a look at the post introducing Astro, though if you want to know more about partial hydration and Astro’s islands architecture.
If you are new to Astro, consider this a gentle introduction. If, however, you already have some experience with Astro you will see a new and efficient way to add SVG icons to your Astro app. This will let you pick icons from any icon library you want, just by adding a single dependency.
🚀 Getting Started #
The code we will look at can easily be added to an existing project, though if you are new to Astro, just follow along and you can add it to your next project! If you are starting a new project, let’s get the ball rolling in the Terminal:
pnpm create astro@latest astro-scroll-to-anchorcd astro-scroll-to-anchorpnpm installpnpm astro telemetry disablepnpm dev
Use yarn
or npm
if you prefer those
to pnpm
. Choose Minimal from the list of templates.
We disable telemetry here. Skip that line if you want to keep data collection. The Astro dev server
will normally run on port 4321
but if there is already something running
there, it will find the next available port. The terminal will then tell you which port it settled
for:
![Astro Scroll to Anchor: Screenshot show Astro C L I terminal output giving the dev server address as http localhost 3001](https://rodneylab-nebula-a-1zqt.shuttle.app/v1/lab/post/astro-scroll-to-anchor/astro-scroll-to-anchor-terminal.468ee9639019.png?w=592&h=368&fit=min&auto=format)
This is a great feature, just make sure you only run one server at a time though! You can run multiple servers, but a couple of times I have spun up a new dev server, while I already had one running in preview mode. Of course, the preview one was open in the browser and I couldn't work out why code changes weren't showing up… a fun way to waste ten minutes!
Anyway, if you are following along, starting from scratch, replace the content in src/pages/index.astro
with this:
src/pages/index.astro
— click to expand code.
1 ---2 // frontmatter section - nothing to see here yet3 ---45 <html lang="en-GB">6 <head>7 <meta charset="utf-8" />8 <meta name="viewport" content="width=device-width" />9 <title>Astro Scroll to Anchor</title>10 </head>11 <body>12 <main class="container">13 <div class="wrapper">14 <h1>15 <Heading text="Astro Scroll to Anchor" id="astro-scroll-to-anchor" />16 </h1>17 <h2>Lorem ipsum" /></h2>18 <p>19 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt20 ut labore et dolore magna aliqua. Suscipit adipiscing bibendum est ultricies integer quis.21 Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. At erat pellentesque22 adipiscing commodo elit at imperdiet. Suscipit adipiscing bibendum est ultricies integer23 quis auctor. Velit sed ullamcorper morbi tincidunt ornare massa eget egestas. Imperdiet24 sed euismod nisi porta. Non blandit massa enim nec. Etiam dignissim diam quis enim25 lobortis scelerisque fermentum dui. Suspendisse sed nisi lacus sed viverra tellus in.26 Metus dictum at tempor commodo ullamcorper a. A scelerisque purus semper eget duis at.27 Ultrices dui sapien eget mi proin sed libero. Cursus metus aliquam eleifend mi in nulla28 posuere sollicitudin.29 </p>30 <h2>Amet porttitor</h2>31 <p>32 Amet porttitor eget dolor morbi. Ullamcorper eget nulla facilisi etiam dignissim diam quis33 enim. Cras tincidunt lobortis feugiat vivamus at. Eleifend donec pretium vulputate sapien34 nec sagittis aliquam malesuada bibendum. Curabitur gravida arcu ac tortor dignissim.35 Scelerisque purus semper eget duis. Amet nulla facilisi morbi tempus iaculis urna id. Et36 ligula ullamcorper malesuada proin libero. Risus pretium quam vulputate dignissim37 suspendisse in. Nec dui nunc mattis enim ut tellus elementum. At quis risus sed vulputate38 odio. Facilisi cras fermentum odio eu feugiat pretium. Lorem ipsum dolor sit amet39 consectetur. Sit amet massa vitae tortor condimentum lacinia quis. Amet volutpat consequat40 mauris nunc congue nisi vitae suscipit tellus. Posuere lorem ipsum dolor sit amet41 consectetur adipiscing elit duis. Ac turpis egestas integer eget aliquet nibh. In nibh42 mauris cursus mattis.43 </p>44 <h2>Blandit turpis</h2>45 <p>46 Blandit turpis cursus in hac habitasse platea. Egestas tellus rutrum tellus pellentesque47 eu. In eu mi bibendum neque. Accumsan in nisl nisi scelerisque eu ultrices vitae auctor.48 Augue mauris augue neque gravida. Tristique nulla aliquet enim tortor at auctor. A49 pellentesque sit amet porttitor. Pharetra pharetra massa massa ultricies mi. Fringilla ut50 morbi tincidunt augue interdum velit euismod in pellentesque. Et leo duis ut diam quam51 nulla porttitor. Pharetra diam sit amet nisl suscipit. Lorem donec massa sapien faucibus.52 Tempor orci eu lobortis elementum nibh tellus. Urna porttitor rhoncus dolor purus non enim53 praesent elementum facilisis. Sed nisi lacus sed viverra tellus in hac habitasse.54 Fermentum leo vel orci porta non pulvinar neque laoreet suspendisse. Enim facilisis55 gravida neque convallis a cras. Enim nunc faucibus a pellentesque sit amet porttitor. Cras56 fermentum odio eu feugiat pretium.57 </p>58 <h2>Arcu dui</h2>59 <p>60 Arcu dui vivamus arcu felis bibendum ut tristique. Congue eu consequat ac felis donec et61 odio. Semper feugiat nibh sed pulvinar proin gravida hendrerit. Libero nunc consequat62 interdum varius sit. At volutpat diam ut venenatis. Euismod quis viverra nibh cras.63 Vestibulum morbi blandit cursus risus. Risus viverra adipiscing at in tellus integer64 feugiat scelerisque. Tristique senectus et netus et malesuada fames ac. Amet risus nullam65 eget felis eget nunc lobortis. Nisl pretium fusce id velit ut tortor pretium viverra.66 Turpis egestas sed tempus urna et pharetra pharetra massa massa. Fermentum dui faucibus in67 ornare quam viverra orci sagittis. Nam libero justo laoreet sit. Eget velit aliquet68 sagittis id consectetur purus ut faucibus pulvinar. Nullam ac tortor vitae purus faucibus69 ornare suspendisse.70 </p>71 <h2>Tellus in hac</h2>72 <p>73 Tellus in hac habitasse platea dictumst vestibulum rhoncus est pellentesque. Dignissim74 sodales ut eu sem integer vitae justo. Nunc vel risus commodo viverra. Nunc sed blandit75 libero volutpat sed cras. Arcu risus quis varius quam quisque id. Tristique sollicitudin76 nibh sit amet commodo nulla facilisi. Sed vulputate mi sit amet mauris commodo quis77 imperdiet. Tristique sollicitudin nibh sit amet commodo nulla facilisi. Tellus at urna78 condimentum mattis. Feugiat scelerisque varius morbi enim. Sit amet aliquam id diam79 maecenas ultricies mi. Lectus quam id leo in vitae turpis massa sed. Felis donec et odio80 pellentesque diam volutpat commodo sed egestas. Facilisis gravida neque convallis a cras81 semper. Velit laoreet id donec ultrices tincidunt. Sed lectus vestibulum mattis82 ullamcorper velit. Et ultrices neque ornare aenean euismod elementum nisi quis eleifend.83 </p>84 </div>85 </main>86 </body>87 </html>8889 <style>90 /* raleway-regular - latin */91 @font-face {92 font-family: 'Raleway';93 font-style: normal;94 font-weight: 400;95 src: local(''), url('/fonts/raleway-v26-latin-regular.woff2') format('woff2');96 }9798 @font-face {99 font-family: 'Raleway';100 font-style: normal;101 font-weight: 700;102 src: local(''), url('/fonts/raleway-v26-latin-700.woff2') format('woff2');103 }104105 @font-face {106 font-family: 'Raleway';107 font-style: normal;108 font-weight: 900;109 src: local(''), url('/fonts/raleway-v26-latin-900.woff2') format('woff2');110 }111112 :global(html) {113 --colour-background-hue: 47.36;114 --colour-background-saturation: 100%;115 --colour-background-luminance: 52.55%;116117 --colour-text-hue: 282.86;118 --colour-text-saturation: 53.85%;119 --colour-text-luminance: 35.69%;120121 --font-family-heading: Montserrat;122 --font-family-body: Raleway;123124 --font-size-1: 1rem;125 --font-size-5: 2.441rem;126 --font-size-6: 3.052rem;127128 --font-weight-bold: 700;129 --font-weight-black: 900;130131 --line-height-relaxed: 1.75;132133 --max-width-full: 100%;134 --max-width-wrapper: 38rem;135136 --spacing-20: 5rem;137138 background-color: hsl(139 var(--colour-background-hue) var(--colour-background-saturation)140 var(--colour-background-luminance)141 );142 color: hsl(var(--colour-text-hue) var(--colour-text-saturation) var(--colour-text-luminance));143 }144145 :global(h1),146 :global(h2) {147 font-family: var(--font-family-heading);148 }149150 :global(h1) {151 font-size: var(--font-size-6);152 font-weight: var(--font-weight-black);153 }154 :global(h2) {155 font-size: var(--font-size-5);156 font-weight: var(--font-weight-bold);157 }158159 :global(p) {160 font-family: var(--font-family-body);161 font-size: var(--font-size-1);162 line-height: var(--line-height-relaxed);163 }164165 .container {166 display: flex;167 align-items: center;168 padding-bottom: var(--spacing-20);169 }170171 .wrapper {172 width: var(--max-width-full);173 max-width: var(--max-width-wrapper);174 margin: 0 auto;175 }176 </style>
This is just some placeholder text which will let us explore a few Astro features as we build out the Astro scroll to anchor feature.
Anatomy of an Astro File #
Like Markdown files, Astro files also have a front matter section. This is where you can import packages as well as run any JavaScript you need to for the output. You can also use TypeScript in the front matter.
The next part of the file is essentially a template. You can include JavaScript scripts in script tags, but can't actually run JavaScript code within this section (this is different to the JSX you might use in React, for example). The Astro markup is a superset of HTML meaning making it easy to pick up if you are coming from a HTML/JavaScript only background.
Finally, at the bottom we have some styling. You can include it like this within a script tag in
your Astro file. As an alternative, for a typical project, you can create a global CSS stylesheet
and import that in your Astro frontmatter. In this case you can still include any styles for the
current page in this style tag. If you do want to use global stylesheet, just save it somewhere
within the src
folder of your project and import it as mentioned.
Final Preparation #
Before proceeding, download some self-hosted fonts which we will use on the site. Save raleway-latin-400-normal.woff2 and raleway-latin-700-normal.woff2 together with raleway-latin-900-normal.woff2 to a new, public/fonts
directory within the project.
🧩 Heading Component #
The markup and styling for the Astro scroll to anchor feature will go in a new Astro component
file. You can create Astro components as well as pages. The components are akin to those you would
have in a React or SvelteKit app. Create a src/components
folder and
within that directory make a new Heading.astro
file, adding this content:
1 ---2 import { Icon } from 'astro-icon/components';34 const { 'aria-label': ariaLabel, id, text } = Astro.props;56 const href = `#${id}`;7 ---89 <span {id} class="container">10 {text}11 <a aria-label={ariaLabel} {href}12 ><span class="anchor-link"><Icon name="heroicons-solid:link" /> </span></a13 >14 </span>1516 <style lang="css">17 .anchor-link {18 visibility: hidden;19 }2021 a {22 color: hsl(var(--colour-text-hue) var(--colour-text-saturation) var(--colour-text-luminance));23 text-decoration: none;24 }2526 [astro-icon] {27 display: inline;28 width: var(--font-size-5);29 vertical-align: middle;30 }3132 .container:focus .anchor-link,33 .container:hover .anchor-link {34 visibility: visible;35 }36 </style>
You see a few Astro features in here. Firstly, like our home page we have three sections:
frontmatter, markup and styles. In the first line we import the astro-icon
package by Nate Moore , an Astro maintainer. This makes use of Anthony Fu's fantastic icônes library (used with the iconify package). If you haven’t yet heard of it, it is definitely worth exploring.
Go to the icônes site where you can find icons from just about every library that exists. You can pick
the icons you want for your app and under the hood, astro-icon
efficiently
imports just the ones you need.
We use the icon, in line 12
. You just select the icon you want on
the icônes site and it gives you a name to add which you add as an attribute to the <Icon>
component instances. Before that though, we need to install the astro-icon
package and the icon pack(s) for your chosen icon(s):
pnpm add -D astro-icon @iconify-json/heroicons-solid
and add a few lines of config to astro.config.mjs
in the project root
folder:
1 import icon from 'astro-icon';2 import { defineConfig } from 'astro/config';34 // https://astro.build/config5 export default defineConfig({6 integrations: [7 icon({8 include: {9 'heroicons-solid': ['link'],10 },11 }),12 ],13 });
Here, “link
” in line 9
is the name
of the icon we want to choose from the pack, you can list out all the icons you want to use or, alternatively,
use all icons from the pack.
🗳 Poll #
Astro props #
In line 4
(of the Header.astro
file)
you see how to access props in an Astro component, we will include these in the markup for the home
page in the next step. The two props will be the text of the title together with an id, used to create
the scrolling link. This needs to be unique for each link we create. We actually use the text
prop in the markup, in line 10
.
Moving in line 11
we use an Astro shortcut (this will look familiar
if you know Svelte). We can use this shortcut whenever the name of a variable matches the name of the
attribute we want to assign it to:
<a aria-label={ariaLabel} {href}>
is equivalent to:
<a aria-label={ariaLabel} href={href}>
Finally, in lines 26
– 30
, you see we can style the icon by targetting [astro-icon]
.
Notice the global CSS variables we defined on the home page are available in our component.
🔌 Using the new Component #
Using the new component is a breeze. Let's update src/pages/index.astro
first, importing our new Heading
component:
1 ---2 import Heading from '../components/Heading.astro';3 ---
and then using it in the headings:
13 <div class="wrapper">14 <h1>15 <Heading text="Astro Scroll to Anchor" id="astro-scroll-to-anchor" />16 </h1>17 <h2><Heading id="lorem-ipsum" text="Lorem ipsum" /></h2>18 <p>
30 <h2><Heading id="amet-porttitor" text="Amet porttitor" /></h2>
44 <h2><Heading id="blandit-turpis" text="Blandit turpis" /></h2>
58 <h2><Heading id="arcu-dui" text="Arcu dui" /></h2>
71 <h2><Heading id="tellus-in-hac" text="Tellus in hac" /></h2>
Alternative Implementation #
We are passing the text in as a prop. This is so that you have easier access to the title text in the component, so for example, you could add some code to remove widows. This is the pet peeve of typographers where you have a single short word alone on a line. The alternative is to rewrite the component so it accepts the title text sandwiched between the Heading component:
<!-- EXAMPLE ONLY WILL NOT WORK WITHOUT UPDATING Heading COMPONENT --><h2><Heading id="tellus-in-hac">Tellus in hac</h2>
Then in the heading component, in the markup you would need to replace {text}
with <slot/>
. We won't go into details, here, just
want to let you know another way exists.
If you save and hover over one of the headings, you icon should show up.
🛹 Adding Smooth Scrolling #
The final missing piece is to add a touch of CSS to get smooth scrolling. It might seem counter-intuitive but we will switch off the feature for users who prefer reduced motion. This is just because on a long page, scrolling can be quite quick, and might trigger nausea in site visitors with vestibular disorders.
137 <style>138 :global(html) {139140 /* ...TRUNCATED */141142 color: hsl(var(--colour-text-hue) var(--colour-text-saturation) var(--colour-text-luminance));143144 scroll-behavior: smooth;145 }146 @media (prefers-reduced-motion: reduce) {147 :global(html) {148 scroll-behavior: auto;149 }150 }
That’s it now! Let’s test it out next.
💯 Astro Scroll to Anchor: Testing it out #
Please enable JavaScript to watch the video 📼
🙌🏽 Astro Scroll to Anchor: Wrapping Up #
In this post, we have had an introduction to Astro and seen:
- how to pass props into an Astro component and access them from within the component;
- a convenient and efficient way to add SVG icons to your Astro app; and
- how to make smooth scrolling more accessible.
The full code for the app is available in the Astro demo repo on Rodney Lab GitHub .
I hope you found this article useful and am keen to hear how you plan to use the Astro code in your own projects.
🏁 Astro Scroll to Anchor: Summary #
Do you have to know React or another framework to use Astro? #
- Although Astro works with Lit, React, Svelte and Vue, you don’t have to use any of these if your app doesn't need it. Astro, itself a superset of HTML, is a templating language used for markup. When you don’t need JavaScript in your app, using Astro alone will work just fine! This makes Astro a nice choice of framework if you are moving on from plain HTML and JavaScript apps.
How can you easily add SVG icons in Astro? #
- astro-icon is a great choice for adding SVG icons to your Astro app. It provides an API for all of the icons in the icônes library. That library includes icons from just about every set of icons you could possibly want to use. astro-icon will efficiently import only the icons you use, helping to keep the shipped bundle size small.
How can you make smooth scrolling more accessible? #
- Smooth scrolling looks good on short pages and might not cause accessibility issues. For long pages, the resulting scroll might be quick enough to trigger nausea in sufferers of vestibular disorders. This does not mean that you shouldn’t use the CSS or JavaScript smooth scrolling functions. Instead, just add a media query, setting smooth scrolling to 'auto' if the user has set 'prefers-reduced-motion' to 'reduce' in their browser. Setting 'auto' results in an instant scroll which should provide a better experience for these users.
🙏🏽 Astro Scroll to Anchor: Feedback #
Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also, if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please consider supporting me through Buy me a Coffee.
Just dropped a new post in which you see how you can use astro-icon to create a CSS-only Scroll to Anchor feature in your Astro app.
— Rodney (@askRodney) March 18, 2022
There’s also a tip on how you can make smooth scrolling more accessible.
Hope you find it useful!
https://t.co/ssZTfjWqM0
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter and also askRodney on Telegram . Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as SvelteKit. Also, subscribe to the newsletter to keep up-to-date with our latest projects.