diff --git a/web/index.css b/web/index.css
index 1ff2fc416e86d3e4d9545dba7d869cf2d97f1d8a..1a5b5899b1dc66a555cc168034e19a532439a0b0 100644
--- a/web/index.css
+++ b/web/index.css
@@ -1,64 +1,62 @@
-/*
-Copyright (c) 2020 Tulir Asokan
-
-This Source Code Form is subject to the terms of the Mozilla Public
-License, v. 2.0. If a copy of the MPL was not distributed with this
-file, You can obtain one at http://mozilla.org/MPL/2.0/.
-*/
-
 * {
-    font-family: sans-serif;
-}
+  font-family: sans-serif; }
 
 html {
-    scrollbar-width: none;
-}
+  scrollbar-width: none; }
+  html::-webkit-scrollbar {
+    display: none; }
 
 body {
-    margin: 0;
-}
-
-.main:not(.pack-list) {
-    margin: 2rem;
-}
-
-.main.empty {
-    text-align: center;
-}
-
-.stickerpack > .sticker-list {
-    display: flex;
-    flex-wrap: wrap;
-}
-
-.stickerpack > h1 {
-    margin: .75rem;
-}
+  margin: 0; }
 
-.sticker {
+h1 {
+  font-size: 1rem; }
+
+main.spinner, main.error, main.empty {
+  margin: 2rem; }
+
+main.empty {
+  text-align: center; }
+
+nav {
+  display: flex;
+  overflow-x: auto;
+  scrollbar-width: none;
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 12vw;
+  background-color: white;
+  z-index: 10; }
+  nav::-webkit-scrollbar {
+    display: none; }
+  nav div.sticker {
+    width: 12vw;
+    height: 12vw; }
+
+section.stickerpack {
+  padding-top: 12vw;
+  margin-top: -12vw; }
+  section.stickerpack > div.sticker-list {
     display: flex;
-    padding: 4px;
-    cursor: pointer;
-    position: relative;
-    width: 25vw;
-    height: 25vw;
-    box-sizing: border-box;
-}
-
-.sticker:hover {
-    background-color: #eee;
-}
-
-.sticker > img {
+    flex-wrap: wrap; }
+  section.stickerpack > h1 {
+    margin: .75rem; }
+
+div.sticker {
+  display: flex;
+  padding: 4px;
+  cursor: pointer;
+  position: relative;
+  width: 25vw;
+  height: 25vw;
+  box-sizing: border-box; }
+  div.sticker:hover {
+    background-color: #eee; }
+  div.sticker > img {
     display: none;
     width: 100%;
-    object-fit: contain;
-}
-
-.sticker > img.visible {
-    display: initial;
-}
-
-h1 {
-    font-size: 1rem;
-}
+    object-fit: contain; }
+    div.sticker > img.visible {
+      display: initial; }
diff --git a/web/index.js b/web/index.js
index 86aac0642a6a961732d6c3c61926dfa35a67472a..bc478af0a8534f630e381a2635a2743f0c614488 100644
--- a/web/index.js
+++ b/web/index.js
@@ -24,6 +24,7 @@ class App extends Component {
 			error: null,
 		}
 		this.observer = null
+		this.packListRef = null
 	}
 
 	observeIntersection = intersections => {
@@ -67,7 +68,7 @@ class App extends Component {
 	}
 
 	componentDidUpdate() {
-		for (const elem of document.getElementsByClassName("sticker")) {
+		for (const elem of this.packListRef.getElementsByClassName("sticker")) {
 			this.observer.observe(elem)
 		}
 	}
@@ -78,29 +79,41 @@ class App extends Component {
 
 	render() {
 		if (this.state.loading) {
-			return html`<div class="main spinner"><${Spinner} size=${80} green /></div>`
+			return html`<main class="spinner"><${Spinner} size=${80} green /></main>`
 		} else if (this.state.error) {
-			return html`<div class="main error">
+			return html`<main class="error">
 				<h1>Failed to load packs</h1>
 				<p>${this.state.error}</p>
-			</div>`
+			</main>`
 		} else if (this.state.packs.length === 0) {
-			return html`<div class="main empty"><h1>No packs found :(</h1></div>`
+			return html`<main class="empty"><h1>No packs found :(</h1></main>`
 		}
-		return html`<div class="main pack-list">
-			${this.state.packs.map(pack => html`<${Pack} id=${pack.id} ...${pack}/>`)}
-		</div>`
+		return html`<main>
+			<nav>
+				${this.state.packs.map(pack => html`<${NavBarItem} id=${pack.id} pack=${pack}/>`)}
+			</nav>
+			<div class="pack-list" ref=${elem => this.packListRef = elem}>
+				${this.state.packs.map(pack => html`<${Pack} id=${pack.id} pack=${pack}/>`)}
+			</div>
+		</main>`
 	}
 }
 
-const Pack = ({ title, stickers }) => html`<div class="stickerpack">
-	<h1>${title}</h1>
+const NavBarItem = ({ pack }) => html`<a href="#pack-${pack.id}" title=${pack.title}>
+	<div class="sticker">
+		<img src=${makeThumbnailURL(pack.stickers[0].url)}
+			 alt=${pack.stickers[0].body} class="visible" />
+	 </div>
+</a>`
+
+const Pack = ({ pack }) => html`<section class="stickerpack" id=${`pack-${pack.id}`}>
+	<h1>${pack.title}</h1>
 	<div class="sticker-list">
-		${stickers.map(sticker => html`
+		${pack.stickers.map(sticker => html`
 			<${Sticker} key=${sticker["net.maunium.telegram.sticker"].id} content=${sticker}/>
 		`)}
 	</div>
-</div>`
+</section>`
 
 const Sticker = ({ content }) => html`<div class="sticker" onClick=${() => sendSticker(content)}>
 	<img data-src=${makeThumbnailURL(content.url)} alt=${content.body} />
diff --git a/web/index.sass b/web/index.sass
new file mode 100644
index 0000000000000000000000000000000000000000..fe73eb8c0633bcd1690fc6bb83125c0181040746
--- /dev/null
+++ b/web/index.sass
@@ -0,0 +1,82 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+*
+  font-family: sans-serif
+
+html
+  scrollbar-width: none
+
+  &::-webkit-scrollbar
+    display: none
+
+body
+  margin: 0
+
+h1
+  font-size: 1rem
+
+main
+  &.spinner, &.error, &.empty
+    margin: 2rem
+
+  &.empty
+    text-align: center
+
+nav
+  display: flex
+  overflow-x: auto
+  scrollbar-width: none
+
+  &::-webkit-scrollbar
+    display: none
+
+  position: fixed
+  top: 0
+  left: 0
+  right: 0
+  height: 12vw
+
+  background-color: white
+  z-index: 10
+
+  div.sticker
+    width: 12vw
+    height: 12vw
+
+section.stickerpack
+  // This is a slightly hacky hack so that we can simultaneously have:
+  // * Anchor URLs scroll so the header is visible
+  // * The scroll area is the whole document
+  padding-top: 12vw
+  margin-top: -12vw
+
+  > div.sticker-list
+    display: flex
+    flex-wrap: wrap
+
+  > h1
+    margin: .75rem
+
+div.sticker
+  display: flex
+  padding: 4px
+  cursor: pointer
+  position: relative
+  width: 25vw
+  height: 25vw
+  box-sizing: border-box
+
+  &:hover
+    background-color: #eee
+
+  > img
+    display: none
+    width: 100%
+    object-fit: contain
+
+    &.visible
+      display: initial