Repository: melt-ui/melt-ui
Pull request #584 (2023-10-03 15:27:39)

Add `aria-disabled` to slider root
This PR adds an aria-disabled attributes to the Slider's root element.

PS: sorry for doing 2 PRs, the old branch got messed up, so I deleted it.

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: b8495314ad0e76559b9e4de03729e62a81b72cff

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/abdel-17/melt-ui/new/add-slider-aria-disabled?filename=.changeset/bright-comics-double.md&value=---%0A%22%40melt-ui%2Fsvelte%22%3A%20patch%0A---%0A%0AAdd%20%60aria-disabled%60%20to%20slider%20root%0A)



Patch:
diff --git a/.changeset/fair-yaks-travel.md b/.changeset/fair-yaks-travel.md
new file mode 100644
index 00000000..a4ffa9c4
--- /dev/null
+++ b/.changeset/fair-yaks-travel.md
@@ -0,0 +1,5 @@
+---
+"@melt-ui/svelte": patch
+---
+
+Tooltip: Fix bug with `group` parameter
diff --git a/.changeset/giant-baboons-agree.md b/.changeset/giant-baboons-agree.md
new file mode 100644
index 00000000..78f61b51
--- /dev/null
+++ b/.changeset/giant-baboons-agree.md
@@ -0,0 +1,5 @@
+---
+'@melt-ui/svelte': patch
+---
+
+Link Preview: Fix bug where content would not close
diff --git a/.changeset/hungry-queens-hide.md b/.changeset/hungry-queens-hide.md
new file mode 100644
index 00000000..74ab9f7f
--- /dev/null
+++ b/.changeset/hungry-queens-hide.md
@@ -0,0 +1,5 @@
+---
+"@melt-ui/svelte": minor
+---
+
+- return an `ids` object from builders which contains the `id`s Melt applies to certain elements internally
diff --git a/.changeset/loud-bags-hug.md b/.changeset/loud-bags-hug.md
new file mode 100644
index 00000000..8f5028d1
--- /dev/null
+++ b/.changeset/loud-bags-hug.md
@@ -0,0 +1,5 @@
+---
+"@melt-ui/svelte": patch
+---
+
+Slider: Add `aria-disabled` to root
diff --git a/.changeset/thick-moles-rest.md b/.changeset/thick-moles-rest.md
new file mode 100644
index 00000000..e4d535ef
--- /dev/null
+++ b/.changeset/thick-moles-rest.md
@@ -0,0 +1,5 @@
+---
+'@melt-ui/svelte': patch
+---
+
+Tooltip: fix esc handler
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 00000000..6a405f50
--- /dev/null
+++ b/.gitpod.yml
@@ -0,0 +1,11 @@
+# This configuration file was automatically generated by Gitpod.
+# Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml)
+# and commit this file to your remote git repository to share the goodness with others.
+
+# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart
+
+tasks:
+  - init: pnpm install
+    command: pnpm run dev
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed9904cd..7678407e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # @melt-ui/svelte
 
+## 0.51.1
+
+### Patch Changes
+
+- Select: Fix defaultSelected clash when `multiple` is true ([`d0a1d4c8`](https://github.com/melt-ui/melt-ui/commit/d0a1d4c8514ca298b082f9ff62fe46b52d5d45ad))
+
+- Combobox: Fix defaultSelected clash when `multiple` is true ([`633bacf0`](https://github.com/melt-ui/melt-ui/commit/633bacf062b1affcab92f4148ae4609a1532e747))
+
 ## 0.51.0
 
 ### Minor Changes
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a6d2f0a1..03dc8099 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -124,3 +124,8 @@ broken.
 
 Lastly, run `pnpm run lint` && `pnpm run check` to ensure that everything is in order before
 submitting the pull request.
+
+## Contribute from a browser IDE 
+Make changes and contribute to OctoBot in a single click with an **already setup and ready to code developer environment** using Gitpod !
+
+[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/?autostart=true#https://github.com/melt-ui/melt-ui/tree/develop)
diff --git a/README.md b/README.md
index 6eddb236..493f10d2 100644
--- a/README.md
+++ b/README.md
@@ -4,10 +4,12 @@
 
 [Melt UI](https://www.melt-ui.com/) is a set of headless, accessible component builders for Svelte.
 
-[![](https://img.shields.io/npm/v/@melt-ui/svelte?style=flat-square)](https://www.npmjs.com/package/@melt-ui/svelte)
-[![](https://img.shields.io/github/actions/workflow/status/melt-ui/melt-ui/ci.yaml?style=flat-square)](https://github.com/melt-ui/melt-ui/actions/workflows/ci.yaml)
-![](https://img.shields.io/github/license/melt-ui/melt-ui?style=flat-square)
-[![](https://dcbadge.vercel.app/api/server/2QDjZkYunf?style=flat-square)](https://discord.gg/2QDjZkYunf)
+[![](https://img.shields.io/npm/v/@melt-ui/svelte?style=flat)](https://www.npmjs.com/package/@melt-ui/svelte)
+![npm](https://img.shields.io/npm/dw/%40melt-ui%2Fsvelte?style=flat&color=orange)
+
+[![](https://img.shields.io/github/actions/workflow/status/melt-ui/melt-ui/ci.yaml?style=flat)](https://github.com/melt-ui/melt-ui/actions/workflows/ci.yaml)
+![](https://img.shields.io/github/license/melt-ui/melt-ui?style=flat)
+[![](https://dcbadge.vercel.app/api/server/2QDjZkYunf?style=flat)](https://discord.gg/2QDjZkYunf)
 
 ## About
 
diff --git a/package.json b/package.json
index 7cfbf653..bf31a273 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@melt-ui/svelte",
-	"version": "0.51.0",
+	"version": "0.51.1",
 	"license": "MIT",
 	"repository": "github:melt-ui/melt-ui",
 	"scripts": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f5af42f1..fb4e5482 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12943,4 +12943,4 @@ packages:
 
   /zwitch@2.0.4:
     resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
-    dev: true
+    dev: true
\ No newline at end of file
diff --git a/src/app.html b/src/app.html
index 73fc5a67..9dca4f3f 100644
--- a/src/app.html
+++ b/src/app.html
@@ -4,7 +4,6 @@
 		<meta charset="utf-8" />
 		<link rel="icon" href="%sveltekit.assets%/favicon.png" />
 		<meta name="viewport" content="width=device-width, initial-scale=1" />
-		<link rel="preload" as="image" href="%sveltekit.assets%/logo.svg" />
 		<title>Melt UI</title>
 		<meta
 			name="description"
diff --git a/src/docs/components/contributor.svelte b/src/docs/components/contributor.svelte
new file mode 100644
index 00000000..f693387f
--- /dev/null
+++ b/src/docs/components/contributor.svelte
@@ -0,0 +1,107 @@
+<script lang="ts">
+	import { createAvatar, createLinkPreview, melt } from '$lib';
+	import type { FullContributor } from '$routes/docs/[...slug]/+page.server';
+	import { fly } from 'svelte/transition';
+
+	export let contributor: FullContributor;
+
+	const getInitials = (name: string) => {
+		const [first, last] = name.split(' ');
+		if (last) {
+			return `${first[0]}${last[0]}`.toUpperCase();
+		}
+		return first.slice(0, 2).toUpperCase();
+	};
+
+	const {
+		elements: { image, fallback },
+	} = createAvatar({
+		src: contributor.avatar_url,
+	});
+
+	const {
+		elements: { trigger, content },
+		states: { open },
+	} = createLinkPreview({
+		forceVisible: true,
+		positioning: {
+			placement: 'top',
+		},
+		closeDelay: 100,
+		openDelay: 250,
+	});
+</script>
+
+<a
+	class="avatar"
+	href={contributor.html_url}
+	target="_blank"
+	rel="noopener noreferrer"
+	use:melt={$trigger}
+>
+	<img use:melt={$image} alt="Avatar" class="avatar" />
+	<div use:melt={$fallback} class="fallback">{getInitials(contributor.login)}</div>
+</a>
+
+{#if $open}
+	<div
+		class="z-50 w-[320px] rounded-xl border border-neutral-700/50 bg-neutral-800 p-4 shadow"
+		use:melt={$content}
+		transition:fly={{ duration: 150, y: 4 }}
+	>
+		<div class="flex flex-col gap-4">
+			<img
+				class="h-16 w-16 rounded-full"
+				src={contributor.avatar_url}
+				alt="{contributor.login}'s avatar"
+			/>
+			<div class="flex flex-col">
+				<h3 class="text-lg font-medium">{contributor.name}</h3>
+				<p class="mb-2 text-sm text-neutral-400">{contributor.login}</p>
+				{#if contributor.bio}
+					<p class="mb-2 text-sm text-neutral-300">{contributor.bio}</p>
+				{/if}
+				<p class="text-xs text-neutral-400">
+					<span class="font-semibold">{contributor.contributions}</span> contributions
+				</p>
+			</div>
+		</div>
+	</div>
+{/if}
+
+<style lang="postcss">
+	.avatar {
+		display: block;
+
+		--size: 4rem;
+		width: var(--size);
+		height: var(--size);
+
+		background: theme('colors.neutral.900');
+		border-radius: 100%;
+		border: 2px solid theme('colors.neutral.900');
+
+		display: grid;
+		place-items: center;
+
+		img {
+			border-radius: 100%;
+			width: 100%;
+			height: 100%;
+			object-fit: cover;
+		}
+
+		.fallback {
+			font-size: 1.25rem;
+			font-weight: 600;
+			background-color: theme('colors.neutral.700');
+			color: theme('colors.magnum.300');
+
+			width: 100%;
+			height: 100%;
+			border-radius: 100%;
+			display: grid;
+			place-items: center;
+		}
+	}
+</style>
diff --git a/src/docs/components/contributors.svelte b/src/docs/components/contributors.svelte
new file mode 100644
index 00000000..4ab89b58
--- /dev/null
+++ b/src/docs/components/contributors.svelte
@@ -0,0 +1,16 @@
+<script lang="ts">
+	import { page } from '$app/stores';
+	import type { FullContributor } from '$routes/docs/[...slug]/+page.server';
+
+	import Contributor from './contributor.svelte';
+
+	const contributors = $page.data.contributors as FullContributor[] | undefined;
+</script>
+
+<ul class="ml-2 flex flex-wrap gap-y-4">
+	{#each contributors ?? [] as contributor}
+		<li class="-ml-3">
+			<Contributor {contributor} />
+		</li>
+	{/each}
+</ul>
diff --git a/src/docs/components/index.ts b/src/docs/components/index.ts
index fa45b774..b1460610 100644
--- a/src/docs/components/index.ts
+++ b/src/docs/components/index.ts
@@ -24,3 +24,4 @@ export { default as InfoPopover } from './info-popover.svelte';
 export { default as TypeDialog } from './type-dialog.svelte';
 export { default as Logo } from './logo.svelte';
 export { default as CustomEventDialog } from './custom-event-dialog.svelte';
+export { default as Contributors } from './contributors.svelte';
diff --git a/src/docs/content/builders/dialog.md b/src/docs/content/builders/dialog.md
index a6a5f64c..b7a64c98 100644
--- a/src/docs/content/builders/dialog.md
+++ b/src/docs/content/builders/dialog.md
@@ -20,7 +20,18 @@ At a high level, the anatomy of a dialog looks like this:
 ```svelte
 <script lang="ts">
 	import { createDialog, melt } from '@melt-ui/svelte'
-	const { trigger, portal, overlay, content, title, description, close, open } = createDialog()
+	const { 
+		elements: {
+			trigger, 
+			portalled, 
+			overlay, 
+			content, 
+			title, 
+			description, 
+			close, 
+			open,
+		},
+	} = createDialog();
 </script>
 
 <button use:melt={$trigger}> Open Dialog </button>
@@ -38,6 +49,7 @@ At a high level, the anatomy of a dialog looks like this:
 ```
 
 - **Trigger**: The button(s) that open the dialog
+- **Portalled**: The container that is portalled (to `body`, by default)
 - **Overlay**: The dim background that is typically behind a dialog element.
 - **Content**: Container for the content within the dialog.
   - **Title**: The title of the dialog
@@ -63,9 +75,9 @@ By default, we set the `role` attribute to `dialog`. If you want it to be consid
 dialog, you can set the `role` builder prop to `alertdialog`.
 
 ```ts {2}
-const { trigger, portal, overlay, content } = createDialog({
-	role: 'alertdialog'
-})
+const { /* ... */ } = createDialog({
+	role: 'alertdialog',
+});
 ```
 
 ### Disable Scroll Prevention
@@ -74,9 +86,9 @@ By default, scrolling is prevented on the body when a dialog is open. You can di
 by setting the `preventScroll` builder prop to `false`.
 
 ```ts {2}
-const { trigger, portal, overlay, content } = createDialog({
-	preventScroll: false
-})
+const { /* ... */ } = createDialog({
+	preventScroll: false,
+});
 ```
 
 ### Disable Close on Outside Click
@@ -85,9 +97,9 @@ By default, clicking outside of the dialog will close it. You can disable this b
 the `closeOnOutsideClick` builder prop to `false`.
 
 ```ts {2}
-const { trigger, portal, overlay, content } = createDialog({
-	closeOnOutsideClick: false
-})
+const { /* ... */ } = createDialog({
+	closeOnOutsideClick: false,
+});
 ```
 
 ### Disable Close on Escape
@@ -96,9 +108,9 @@ By default, pressing the escape key will close the dialog. You can disable this
 the `closeOnEscape` builder prop to `false`.
 
 ```ts {2}
-const { trigger, portal, overlay, content } = createDialog({
-	closeOnEscape: false
-})
+const { /* ... */ } = createDialog({
+	closeOnEscape: false,
+});
 ```
 
 ## Example Components
@@ -137,7 +149,7 @@ Dialogs can be nested. For example, here's a dialog that opens another dialog.
 
 ## Accessibility
 
-Adheres to the [Dialog WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog/) &
+Adheres to the [Dialog WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) &
 [Alert Dialog WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/)
 
 <KbdTable {keyboard} />
diff --git a/src/docs/content/introduction.md b/src/docs/content/introduction.md
index ec159d9a..de5fa85c 100644
--- a/src/docs/content/introduction.md
+++ b/src/docs/content/introduction.md
@@ -6,7 +6,7 @@ description:
 ---
 
 <script>
-    import { Construction } from '$docs/components'
+    import { Construction, Contributors } from '$docs/components'
 </script>
 
 Melt UI empowers developers to create accessible UIs that embody their unique style. With a strong
@@ -77,6 +77,13 @@ in your preferred code editor. All our components share a consistent API, promot
 minimizing surprises. The components are also build with SSR in mind, making them a perfect fit for
 SvelteKit.
 
+## Built by the community
+
+Melt UI is an open-source project built by the community for the community. It wouldn't be possible
+if it wasn't for the amazing work of some amazing people.
+
+<Contributors />
+
 ## Inspiration & Credits
 
 We're big fans of open source and love working collaboratively. Over time, we've taken cues &
diff --git a/src/docs/data/builders/dialog.ts b/src/docs/data/builders/dialog.ts
index 2e42246b..f54a12b7 100644
--- a/src/docs/data/builders/dialog.ts
+++ b/src/docs/data/builders/dialog.ts
@@ -73,6 +73,20 @@ const trigger = elementSchema('trigger', {
 	events: dialogEvents['trigger'],
 });
 
+const portalled = elementSchema('portalled', {
+	description: "The element that will be portalled (or moved) to a different location in the DOM based on the `portal` prop value.",
+	dataAttributes: [
+		{
+			name: 'data-portal',
+			value: ATTRS.MELT('portalled'),
+		},
+		{
+			name: 'data-melt-dialog-portalled',
+			value: ATTRS.MELT('portalled'),
+		}
+	],
+});	
+
 const overlay = elementSchema('overlay', {
 	description: 'The overlay element which covers the page when the dialog is open.',
 	dataAttributes: [
@@ -155,7 +169,7 @@ const keyboard: KeyboardSchema = [
 	},
 ];
 
-const schemas = [builder, trigger, overlay, content, close, title, description];
+const schemas = [builder, trigger, portalled, overlay, content, close, title, description];
 const features = [
 	'Fully managed focus',
 	'Can be controlled or uncontrolled',
diff --git a/src/docs/previews/collapsible/main/tailwind/index.svelte b/src/docs/previews/collapsible/main/tailwind/index.svelte
index c82dcac0..ab04f2e4 100644
--- a/src/docs/previews/collapsible/main/tailwind/index.svelte
+++ b/src/docs/previews/collapsible/main/tailwind/index.svelte
@@ -2,58 +2,59 @@
 	import { createCollapsible, melt } from '$lib/index.js';
 	import { slide } from 'svelte/transition';
 	import { ChevronsUpDown, X } from 'lucide-svelte';
-
+  
 	const {
-		elements: { root, content, trigger },
-		states: { open },
+	  elements: { root, content, trigger },
+	  states: { open },
 	} = createCollapsible();
-</script>
-
-<div use:melt={$root} class="mx-auto w-[18rem] max-w-full sm:w-[25rem]">
+  </script>
+  
+  <div use:melt={$root} class="mx-auto w-[18rem] max-w-full sm:w-[25rem] relative mb-28">
 	<div class="flex items-center justify-between">
-		<span class="text-sm font-semibold text-magnum-900">
-			@thomasglopes starred 3 repositories
-		</span>
-		<button
-			use:melt={$trigger}
-			class="relative h-6 w-6 place-items-center rounded-md bg-white text-sm
-            text-magnum-800 shadow hover:opacity-75 data-[disabled]:cursor-not-allowed
-            data-[disabled]:opacity-75"
-			aria-label="Toggle"
-		>
-			<div class="abs-center">
-				{#if $open}
-					<X class="square-4" />
-				{:else}
-					<ChevronsUpDown class="square-4" />
-				{/if}
-			</div>
-		</button>
+	  <span class="text-sm font-semibold text-magnum-900">
+		@thomasglopes starred 3 repositories
+	  </span>
+	  <button
+		use:melt={$trigger}
+		class="relative h-6 w-6 place-items-center rounded-md bg-white text-sm
+			  text-magnum-800 shadow hover:opacity-75 data-[disabled]:cursor-not-allowed
+			  data-[disabled]:opacity-75"
+		aria-label="Toggle"
+	  >
+		<div class="abs-center">
+		  {#if $open}
+			<X class="square-4" />
+		  {:else}
+			<ChevronsUpDown class="square-4" />
+		  {/if}
+		</div>
+	  </button>
 	</div>
-
+  
 	<div class="my-2 rounded-lg bg-white p-3 shadow">
-		<span class="text-base text-black">melt-ui/melt-ui</span>
+	  <span class="text-base text-black">melt-ui/melt-ui</span>
 	</div>
-
-	{#if $open}
+  
+	<div style="position: absolute; top: calc(100% + 10px); left: 0; right: 0;">
+	  {#if $open}
 		<div use:melt={$content} transition:slide>
-			<div class="flex flex-col gap-2">
-				<div class="rounded-lg bg-white p-3 shadow">
-					<span class="text-base text-black">sveltejs/svelte</span>
-				</div>
-				<div class="rounded-lg bg-white p-3 shadow">
-					<span class="text-base text-black">sveltejs/kit</span>
-				</div>
+		  <div class="flex flex-col gap-2">
+			<div class="rounded-lg bg-white p-3 shadow">
+			  <span class="text-base text-black">sveltejs/svelte</span>
 			</div>
+			<div class="rounded-lg bg-white p-3 shadow">
+			  <span class="text-base text-black">sveltejs/kit</span>
+			</div>
+		  </div>
 		</div>
-	{/if}
-</div>
-
-<style lang="postcss">
+	  {/if}
+	</div>
+  </div>
+  <style lang="postcss">
 	.abs-center {
 		position: absolute;
 		left: 50%;
 		top: 50%;
 		transform: translate(-50%, -50%);
 	}
-</style>
+</style>  
\ No newline at end of file
diff --git a/src/docs/previews/combobox/multi/tailwind/index.svelte b/src/docs/previews/combobox/multi/tailwind/index.svelte
index 02b1dc8c..5c50995b 100644
--- a/src/docs/previews/combobox/multi/tailwind/index.svelte
+++ b/src/docs/previews/combobox/multi/tailwind/index.svelte
@@ -3,15 +3,6 @@
 	import { Check, ChevronDown, ChevronUp } from 'lucide-svelte';
 	import { fly } from 'svelte/transition';
 
-	const {
-		elements: { menu, input, option, label },
-		states: { open, inputValue, touchedInput },
-		helpers: { isSelected },
-	} = createCombobox({
-		forceVisible: true,
-		multiple: true,
-	});
-
 	type Manga = {
 		author: string;
 		title: string;
@@ -71,6 +62,15 @@
 		},
 	];
 
+	const {
+		elements: { menu, input, option, label },
+		states: { open, inputValue, touchedInput, selected },
+		helpers: { isSelected },
+	} = createCombobox({
+		forceVisible: true,
+		multiple: true,
+	});
+
 	$: filteredMangas = $touchedInput
 		? mangas.filter(({ title, author }) => {
 				const normalizedInput = $inputValue.toLowerCase();
diff --git a/src/docs/previews/link-preview/main/tailwind/index.svelte b/src/docs/previews/link-preview/main/tailwind/index.svelte
index bb74302c..df1f2293 100644
--- a/src/docs/previews/link-preview/main/tailwind/index.svelte
+++ b/src/docs/previews/link-preview/main/tailwind/index.svelte
@@ -18,9 +18,9 @@
 	use:melt={$trigger}
 >
 	<img
-		src="/logo-mark.svg"
+		src="/logo_mark.svg"
 		alt="Melt UI Logo"
-		class="h-full w-full rounded-full bg-neutral-900 object-contain p-2"
+		class="h-full w-full rounded-full bg-neutral-900 object-contain p-1"
 	/>
 	<span class="sr-only">Open Melt UI Details</span>
 </a>
@@ -34,9 +34,9 @@
 		<div class="w-[300px] rounded-md bg-white p-5 shadow-sm">
 			<div class="flex flex-col gap-2">
 				<img
-					src="/logo-mark.svg"
+					src="/logo_mark.svg"
 					alt="Melt UI Logo"
-					class="object-fit block h-14 w-14 rounded-full bg-neutral-900 p-2"
+					class="object-fit block h-14 w-14 rounded-full bg-neutral-900 p-1"
 				/>
 				<div class="flex flex-col gap-4">
 					<div>
@@ -50,11 +50,11 @@
 				</div>
 				<div class="flex gap-4">
 					<div class="flex gap-1">
-						<div class="text-neutral-900">618</div>
+						<div class="text-neutral-900">2k</div>
 						<div class="text-neutral-400">Stars</div>
 					</div>
 					<div class="flex gap-1">
-						<div class="text-neutral-900">37</div>
+						<div class="text-neutral-900">103</div>
 						<div class="text-neutral-400">Forks</div>
 					</div>
 				</div>
diff --git a/src/lib/builders/accordion/create.ts b/src/lib/builders/accordion/create.ts
index 3c7b34d1..3dd09ce9 100644
--- a/src/lib/builders/accordion/create.ts
+++ b/src/lib/builders/accordion/create.ts
@@ -233,6 +233,7 @@ export const createAccordion = <Multiple extends boolean = false>(
 	}
 
 	return {
+		ids,
 		elements: {
 			root,
 			item,
diff --git a/src/lib/builders/combobox/create.ts b/src/lib/builders/combobox/create.ts
index d1435f83..38dea244 100644
--- a/src/lib/builders/combobox/create.ts
+++ b/src/lib/builders/combobox/create.ts
@@ -165,7 +165,9 @@ export function createCombobox<
 			const $multiple = get(multiple);
 			if ($multiple) {
 				const optionArr = Array.isArray($option) ? $option : [];
-				return toggle(newOption, optionArr) as S;
+				return toggle(newOption, optionArr, (itemA, itemB) =>
+					deepEqual(itemA.value, itemB.value)
+				) as S;
 			}
 			return newOption as S;
 		});
@@ -566,7 +568,7 @@ export function createCombobox<
 			([$selected]) =>
 			(props: ComboboxItemProps<Value>) => {
 				const selected = Array.isArray($selected)
-					? $selected.map((o) => o.value).includes(props.value)
+					? $selected.some((o) => deepEqual(o.value, props.value))
 					: deepEqual($selected?.value, props.value);
 
 				return {
@@ -652,6 +654,7 @@ export function createCombobox<
 	});
 
 	return {
+		ids,
 		elements: {
 			input,
 			option,
diff --git a/src/lib/builders/context-menu/create.ts b/src/lib/builders/context-menu/create.ts
index 5b0d4bfe..799d5bc1 100644
--- a/src/lib/builders/context-menu/create.ts
+++ b/src/lib/builders/context-menu/create.ts
@@ -73,7 +73,7 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 		arrow,
 		createSubmenu,
 		createMenuRadioGroup,
-		rootIds,
+		ids,
 		separator,
 		handleTypeaheadSearch,
 		group,
@@ -111,12 +111,12 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 		const target = e.target;
 		if (!(target instanceof Element)) return;
 
-		if (target.id === rootIds.trigger && isLeftClick(e)) {
+		if (target.id === ids.trigger && isLeftClick(e)) {
 			rootOpen.set(false);
 			return;
 		}
 
-		if (target.id !== rootIds.trigger && !target.closest(selector())) {
+		if (target.id !== ids.trigger && !target.closest(selector())) {
 			rootOpen.set(false);
 		}
 	}
@@ -137,8 +137,8 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 				style: styleToString({
 					display: $isVisible ? undefined : 'none',
 				}),
-				id: rootIds.menu,
-				'aria-labelledby': rootIds.trigger,
+				id: ids.menu,
+				'aria-labelledby': ids.trigger,
 				'data-state': $isVisible ? 'open' : 'closed',
 				'data-portal': $portal ? '' : undefined,
 				tabindex: -1,
@@ -233,10 +233,10 @@ export function createContextMenu(props?: CreateContextMenuProps) {
 		stores: rootOpen,
 		returned: ($rootOpen) => {
 			return {
-				'aria-controls': rootIds.menu,
+				'aria-controls': ids.menu,
 				'aria-expanded': $rootOpen,
 				'data-state': $rootOpen ? 'open' : 'closed',
-				id: rootIds.trigger,
+				id: ids.trigger,
 				style: styleToString({
 					WebkitTouchCallout: 'none',
 				}),
diff --git a/src/lib/builders/dialog/create.ts b/src/lib/builders/dialog/create.ts
index b6e06c48..9be5f518 100644
--- a/src/lib/builders/dialog/create.ts
+++ b/src/lib/builders/dialog/create.ts
@@ -329,6 +329,7 @@ export function createDialog(props?: CreateDialogProps) {
 	});
 
 	return {
+		ids,
 		elements: {
 			content,
 			trigger,
diff --git a/src/lib/builders/link-preview/create.ts b/src/lib/builders/link-preview/create.ts
index bafd8bbb..c8742798 100644
--- a/src/lib/builders/link-preview/create.ts
+++ b/src/lib/builders/link-preview/create.ts
@@ -53,6 +53,9 @@ export function createLinkPreview(props: CreateLinkPreviewProps = {}) {
 	const containSelection = writable(false);
 	const activeTrigger = writable<HTMLElement | null>(null);
 
+	// type OpenReason = 'pointer' | 'focus';
+	// const openReason = writable<null | OpenReason>(null);
+
 	const options = toWritableStores(withDefaults);
 
 	const {
@@ -278,7 +281,10 @@ export function createLinkPreview(props: CreateLinkPreviewProps = {}) {
 	});
 
 	effect([open], ([$open]) => {
-		if (!isBrowser || !$open) return;
+		if (!isBrowser || !$open) {
+			hasSelection.set(false);
+			return;
+		}
 
 		const handlePointerUp = () => {
 			containSelection.set(false);
@@ -307,6 +313,7 @@ export function createLinkPreview(props: CreateLinkPreviewProps = {}) {
 	});
 
 	return {
+		ids,
 		elements: {
 			trigger,
 			content,
diff --git a/src/lib/builders/menu/create.ts b/src/lib/builders/menu/create.ts
index 2e5ff259..7fc150de 100644
--- a/src/lib/builders/menu/create.ts
+++ b/src/lib/builders/menu/create.ts
@@ -1028,6 +1028,7 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 		});
 
 		return {
+			ids: subIds,
 			elements: {
 				subTrigger,
 				subMenu,
@@ -1316,6 +1317,7 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 	}
 
 	return {
+		ids: rootIds,
 		trigger: rootTrigger,
 		menu: rootMenu,
 		open: rootOpen,
@@ -1328,7 +1330,6 @@ export function createMenuBuilder(opts: _MenuBuilderOptions) {
 		createSubmenu,
 		createMenuRadioGroup,
 		separator,
-		rootIds,
 		handleTypeaheadSearch,
 	};
 }
diff --git a/src/lib/builders/menubar/create.ts b/src/lib/builders/menubar/create.ts
index 8904156f..304d6179 100644
--- a/src/lib/builders/menubar/create.ts
+++ b/src/lib/builders/menubar/create.ts
@@ -61,7 +61,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 	const closeTimer = writable(0);
 	let scrollRemoved = false;
 
-	const rootIds = {
+	const ids = {
 		menubar: generateId(),
 	};
 
@@ -71,7 +71,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 				role: 'menubar',
 				'data-melt-menubar': '',
 				'data-orientation': 'horizontal',
-				id: rootIds.menubar,
+				id: ids.menubar,
 			};
 		},
 		action: (node: HTMLElement) => {
@@ -137,10 +137,10 @@ export function createMenubar(props?: CreateMenubarProps) {
 					style: styleToString({
 						display: $isVisible ? undefined : 'none',
 					}),
-					id: m.rootIds.menu,
-					'aria-labelledby': m.rootIds.trigger,
+					id: m.ids.menu,
+					'aria-labelledby': m.ids.trigger,
 					'data-state': $isVisible ? 'open' : 'closed',
-					'data-melt-scope': rootIds.menubar,
+					'data-melt-scope': ids.menubar,
 					'data-portal': $portal ? '' : undefined,
 					tabindex: -1,
 				} as const;
@@ -164,7 +164,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 									clickOutside: {
 										ignore: (e) => {
 											const target = e.target;
-											const menubarEl = document.getElementById(rootIds.menubar);
+											const menubarEl = document.getElementById(ids.menubar);
 											if (!menubarEl || !isElement(target)) return false;
 											return menubarEl.contains(target);
 										},
@@ -239,10 +239,10 @@ export function createMenubar(props?: CreateMenubarProps) {
 			stores: [rootOpen],
 			returned: ([$rootOpen]) => {
 				return {
-					'aria-controls': m.rootIds.menu,
+					'aria-controls': m.ids.menu,
 					'aria-expanded': $rootOpen,
 					'data-state': $rootOpen ? 'open' : 'closed',
-					id: m.rootIds.trigger,
+					id: m.ids.trigger,
 					'aria-haspopup': 'menu',
 					'data-orientation': 'horizontal',
 					role: 'menuitem',
@@ -251,7 +251,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 			action: (node: HTMLElement): MeltActionReturn<MenubarEvents['trigger']> => {
 				applyAttrsIfDisabled(node);
 
-				const menubarEl = document.getElementById(rootIds.menubar);
+				const menubarEl = document.getElementById(ids.menubar);
 				if (!menubarEl) return {};
 
 				const menubarTriggers = Array.from(
@@ -313,7 +313,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 						const $rootOpen = get(rootOpen);
 						if ($activeMenu && !$rootOpen) {
 							rootOpen.set(true);
-							activeMenu.set(m.rootIds.menu);
+							activeMenu.set(m.ids.menu);
 							rootActiveTrigger.set(triggerEl);
 						}
 					})
@@ -335,7 +335,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 					nextFocusable.set(getNextFocusable(triggerEl));
 					prevFocusable.set(getPreviousFocusable(triggerEl));
 					rootActiveTrigger.set(triggerEl);
-					activeMenu.set(m.rootIds.menu);
+					activeMenu.set(m.ids.menu);
 				} else {
 					rootActiveTrigger.set(null);
 				}
@@ -346,10 +346,10 @@ export function createMenubar(props?: CreateMenubarProps) {
 
 		effect([activeMenu], ([$activeMenu]) => {
 			if (!isBrowser) return;
-			if ($activeMenu === m.rootIds.menu) {
+			if ($activeMenu === m.ids.menu) {
 				if (get(rootOpen)) return;
 
-				const triggerEl = document.getElementById(m.rootIds.trigger);
+				const triggerEl = document.getElementById(m.ids.trigger);
 				if (!triggerEl) return;
 				rootActiveTrigger.set(triggerEl);
 				addHighlight(triggerEl);
@@ -357,10 +357,10 @@ export function createMenubar(props?: CreateMenubarProps) {
 				return;
 			}
 
-			if ($activeMenu !== m.rootIds.menu) {
+			if ($activeMenu !== m.ids.menu) {
 				if (!isBrowser) return;
 				if (get(rootOpen)) {
-					const triggerEl = document.getElementById(m.rootIds.trigger);
+					const triggerEl = document.getElementById(m.ids.trigger);
 					if (!triggerEl) return;
 					rootActiveTrigger.set(null);
 					rootOpen.set(false);
@@ -372,9 +372,9 @@ export function createMenubar(props?: CreateMenubarProps) {
 
 		effect([rootOpen], ([$rootOpen]) => {
 			if (!isBrowser) return;
-			const triggerEl = document.getElementById(m.rootIds.trigger);
+			const triggerEl = document.getElementById(m.ids.trigger);
 			if (!triggerEl) return;
-			if (!$rootOpen && get(activeMenu) === m.rootIds.menu) {
+			if (!$rootOpen && get(activeMenu) === m.ids.menu) {
 				rootActiveTrigger.set(null);
 				activeMenu.set('');
 				removeHighlight(triggerEl);
@@ -388,13 +388,14 @@ export function createMenubar(props?: CreateMenubarProps) {
 
 		onMount(() => {
 			if (!isBrowser) return;
-			const triggerEl = document.getElementById(m.rootIds.trigger);
+			const triggerEl = document.getElementById(m.ids.trigger);
 			if (isHTMLElement(triggerEl) && get(rootOpen)) {
 				rootActiveTrigger.set(triggerEl);
 			}
 		});
 
 		return {
+			ids,
 			elements: {
 				menu,
 				trigger,
@@ -446,7 +447,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 		if (isPrevKey && isKeyDownInsideSubMenu) return;
 
 		// Index of the currently focused item in the candidate nodes array
-		const menubarEl = document.getElementById(rootIds.menubar);
+		const menubarEl = document.getElementById(ids.menubar);
 		if (!isHTMLElement(menubarEl)) return;
 		const triggers = getMenuTriggers(menubarEl);
 		const currTriggerId = currentTarget.getAttribute('aria-labelledby');
@@ -545,7 +546,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 	onMount(() => {
 		if (!isBrowser) return;
 
-		const menubarEl = document.getElementById(rootIds.menubar);
+		const menubarEl = document.getElementById(ids.menubar);
 		if (!menubarEl) return;
 		const unsubEvents = executeCallbacks(
 			addMeltEventListener(menubarEl, 'keydown', (e) => {
@@ -601,6 +602,7 @@ export function createMenubar(props?: CreateMenubarProps) {
 	});
 
 	return {
+		ids: ids,
 		elements: {
 			menubar,
 		},
diff --git a/src/lib/builders/pin-input/create.ts b/src/lib/builders/pin-input/create.ts
index 29949c39..96483eed 100644
--- a/src/lib/builders/pin-input/create.ts
+++ b/src/lib/builders/pin-input/create.ts
@@ -249,6 +249,7 @@ export function createPinInput(props?: CreatePinInputProps) {
 	};
 
 	return {
+		ids,
 		elements: {
 			root,
 			input,
diff --git a/src/lib/builders/popover/create.ts b/src/lib/builders/popover/create.ts
index c779e1f8..3092c4a7 100644
--- a/src/lib/builders/popover/create.ts
+++ b/src/lib/builders/popover/create.ts
@@ -254,6 +254,7 @@ export function createPopover(args?: CreatePopoverProps) {
 		};
 	});
 	return {
+		ids,
 		elements: {
 			trigger,
 			content,
diff --git a/src/lib/builders/select/create.ts b/src/lib/builders/select/create.ts
index 4936723c..f9650c73 100644
--- a/src/lib/builders/select/create.ts
+++ b/src/lib/builders/select/create.ts
@@ -558,7 +558,9 @@ export function createSelect<
 			const $multiple = get(multiple);
 			if ($multiple) {
 				const optionArr = Array.isArray($option) ? $option : [];
-				return toggle(newOption, optionArr) as S;
+				return toggle(newOption, optionArr, (itemA, itemB) =>
+					deepEqual(itemA.value, itemB.value)
+				) as S;
 			}
 			return newOption as S;
 		});
@@ -569,7 +571,7 @@ export function createSelect<
 		returned: ($selected) => {
 			return (props: SelectOptionProps<Value>) => {
 				const isSelected = Array.isArray($selected)
-					? $selected.map((o) => o.value).includes(props.value)
+					? $selected.some((o) => deepEqual(o.value, props.value))
 					: deepEqual($selected?.value, props?.value);
 
 				return {
@@ -754,6 +756,7 @@ export function createSelect<
 	});
 
 	return {
+		ids,
 		elements: {
 			menu,
 			trigger,
diff --git a/src/lib/builders/slider/create.ts b/src/lib/builders/slider/create.ts
index 2bfdc820..11dd18b0 100644
--- a/src/lib/builders/slider/create.ts
+++ b/src/lib/builders/slider/create.ts
@@ -1,6 +1,7 @@
 import {
 	addEventListener,
 	addMeltEventListener,
+	ariaDisabledAttr,
 	builder,
 	createElHelpers,
 	disabledAttr,
@@ -55,6 +56,7 @@ export const createSlider = (props?: CreateSliderProps) => {
 		returned: ([$disabled, $orientation]) => {
 			return {
 				disabled: disabledAttr($disabled),
+				'aria-disabled': ariaDisabledAttr($disabled),
 				'data-orientation': $orientation,
 				style: $disabled ? undefined : 'touch-action: none;',
 				'data-melt-id': ids.root,
@@ -426,6 +428,7 @@ export const createSlider = (props?: CreateSliderProps) => {
 	);
 
 	return {
+		ids,
 		elements: {
 			root,
 			thumb,
diff --git a/src/lib/builders/tags-input/create.ts b/src/lib/builders/tags-input/create.ts
index 49fadea4..cfe5a243 100644
--- a/src/lib/builders/tags-input/create.ts
+++ b/src/lib/builders/tags-input/create.ts
@@ -744,6 +744,7 @@ export function createTagsInput(props?: CreateTagsInputProps) {
 	});
 
 	return {
+		ids,
 		elements: {
 			root,
 			input,
diff --git a/src/lib/builders/tooltip/create.ts b/src/lib/builders/tooltip/create.ts
index 755e90aa..52823f8b 100644
--- a/src/lib/builders/tooltip/create.ts
+++ b/src/lib/builders/tooltip/create.ts
@@ -134,6 +134,17 @@ export function createTooltip(props?: CreateTooltipProps) {
 			};
 		},
 		action: (node: HTMLElement): MeltActionReturn<TooltipEvents['trigger']> => {
+			const keydownHandler = (e: KeyboardEvent) => {
+				if (get(closeOnEscape) && e.key === kbd.ESCAPE) {
+					if (openTimeout) {
+						window.clearTimeout(openTimeout);
+						openTimeout = null;
+					}
+
+					open.set(false);
+				}
+			};
+
 			const unsub = executeCallbacks(
 				addMeltEventListener(node, 'pointerdown', () => {
 					const $closeOnPointerDown = get(closeOnPointerDown);
@@ -161,16 +172,8 @@ export function createTooltip(props?: CreateTooltipProps) {
 					openTooltip('focus');
 				}),
 				addMeltEventListener(node, 'blur', () => closeTooltip(true)),
-				addMeltEventListener(node, 'keydown', (e) => {
-					if (get(closeOnEscape) && e.key === kbd.ESCAPE) {
-						if (openTimeout) {
-							window.clearTimeout(openTimeout);
-							openTimeout = null;
-						}
-
-						open.set(false);
-					}
-				})
+				addMeltEventListener(node, 'keydown', keydownHandler),
+				addEventListener(document, 'keydown', keydownHandler)
 			);
 
 			return {
@@ -258,18 +261,24 @@ export function createTooltip(props?: CreateTooltipProps) {
 	let isMouseInTooltipArea = false;
 
 	effect(open, ($open) => {
-		if (!$open) return;
+		const currentGroup = get(group);
+		if (currentGroup === undefined || currentGroup === false) {
+			return;
+		}
 
-		const groupValue = get(group);
-		if (groupValue === undefined || groupValue === false) {
+		if (!$open) {
+			if (groupMap.get(currentGroup) === open) {
+				// Tooltip is no longer open
+				groupMap.delete(currentGroup);
+			}
 			return;
 		}
 
 		// Close the currently open tooltip in the same group
 		// and set this tooltip as the open one.
-		const currentOpen = groupMap.get(groupValue);
+		const currentOpen = groupMap.get(currentGroup);
 		currentOpen?.set(false);
-		groupMap.set(groupValue, open);
+		groupMap.set(currentGroup, open);
 	});
 
 	effect([open, openReason], ([$open, $openReason]) => {
@@ -303,6 +312,7 @@ export function createTooltip(props?: CreateTooltipProps) {
 	});
 
 	return {
+		ids,
 		elements: {
 			trigger,
 			content,
diff --git a/src/lib/builders/tree/create.ts b/src/lib/builders/tree/create.ts
index be01f2c0..406af17b 100644
--- a/src/lib/builders/tree/create.ts
+++ b/src/lib/builders/tree/create.ts
@@ -67,7 +67,7 @@ export function createTreeView(args?: CreateTreeViewProps) {
 		return (itemId: string) => $expanded.includes(itemId);
 	});
 
-	const rootIds = {
+	const ids = {
 		tree: generateId(),
 	};
 
@@ -75,7 +75,7 @@ export function createTreeView(args?: CreateTreeViewProps) {
 		returned: () => {
 			return {
 				role: 'tree',
-				'data-melt-id': rootIds.tree,
+				'data-melt-id': ids.tree,
 			} as const;
 		},
 	});
@@ -124,7 +124,7 @@ export function createTreeView(args?: CreateTreeViewProps) {
 						return;
 					}
 
-					const rootEl = getElementByMeltId(rootIds.tree);
+					const rootEl = getElementByMeltId(ids.tree);
 					if (!rootEl || !isHTMLElement(node) || node.getAttribute('role') !== 'treeitem') {
 						return;
 					}
@@ -279,7 +279,7 @@ export function createTreeView(args?: CreateTreeViewProps) {
 
 	function getItems(): HTMLElement[] {
 		let items = [] as HTMLElement[];
-		const rootEl = getElementByMeltId(rootIds.tree);
+		const rootEl = getElementByMeltId(ids.tree);
 		if (!rootEl) return items;
 
 		// Select all 'treeitem' li elements within our root element.
@@ -322,6 +322,7 @@ export function createTreeView(args?: CreateTreeViewProps) {
 	}
 
 	return {
+		ids,
 		elements: {
 			tree: rootTree,
 			item,
diff --git a/src/lib/internal/helpers/array.ts b/src/lib/internal/helpers/array.ts
index dbed1e77..5aa7857b 100644
--- a/src/lib/internal/helpers/array.ts
+++ b/src/lib/internal/helpers/array.ts
@@ -80,8 +80,12 @@ export function wrapArray<T>(array: T[], startIndex: number): T[] {
 	return array.map((_, index) => array[(startIndex + index) % array.length]);
 }
 
-export function toggle<T>(item: T, array: T[]): T[] {
-	const itemIdx = array.findIndex((i) => deepEqual(i, item));
+export function toggle<T>(
+	item: T,
+	array: T[],
+	compare: (itemA: T, itemB: T) => boolean = deepEqual
+): T[] {
+	const itemIdx = array.findIndex((innerItem) => compare(innerItem, item));
 	if (itemIdx !== -1) {
 		array.splice(itemIdx, 1);
 	} else {
diff --git a/src/lib/internal/helpers/attr.ts b/src/lib/internal/helpers/attr.ts
index 2d2ad64b..915b23bc 100644
--- a/src/lib/internal/helpers/attr.ts
+++ b/src/lib/internal/helpers/attr.ts
@@ -1,3 +1,7 @@
 export function disabledAttr(disabled: boolean | undefined) {
 	return disabled ? true : undefined;
 }
+
+export function ariaDisabledAttr(disabled: boolean | undefined) {
+	return disabled ? 'true' : undefined;
+}
diff --git a/src/routes/docs/[...slug]/+page.server.ts b/src/routes/docs/[...slug]/+page.server.ts
new file mode 100644
index 00000000..eb6002e7
--- /dev/null
+++ b/src/routes/docs/[...slug]/+page.server.ts
@@ -0,0 +1,81 @@
+import { navConfig } from '$docs/config.js';
+import { GITHUB_TOKEN } from '$env/static/private';
+import type { EntryGenerator, PageLoad } from './$types.js';
+
+export const entries = (() => {
+	return navConfig.sidebarNav[0].items.map((item) => {
+		return { slug: item.title.toLowerCase().replaceAll(' ', '-') };
+	});
+}) satisfies EntryGenerator;
+
+export type Contributor = {
+	login: string;
+	id: number;
+	node_id: string;
+	avatar_url: string;
+	gravatar_id: string;
+	url: string;
+	html_url: string;
+	followers_url: string;
+	following_url: string;
+	gists_url: string;
+	starred_url: string;
+	subscriptions_url: string;
+	organizations_url: string;
+	repos_url: string;
+	events_url: string;
+	received_events_url: string;
+	type: string;
+	site_admin: boolean;
+	contributions: number;
+};
+
+export type FullContributor = Contributor & { name: string; bio: string };
+
+let storedContributors: FullContributor[] | undefined;
+
+const getContributors = async () => {
+	if (!storedContributors) {
+		try {
+			// eslint-disable-next-line no-console
+			console.log('Fetching contributors...');
+			const res = await fetch(`https://api.github.com/repos/melt-ui/melt-ui/contributors`, {
+				headers: {
+					Authorization: `Bearer ${GITHUB_TOKEN}`,
+				},
+			});
+
+			const contributors = (await res.json()) as Contributor[];
+
+			const contributorsWithName = (await Promise.all(
+				contributors.map(async (contributor) => {
+					const res = await fetch(`https://api.github.com/users/${contributor.login}`, {
+						headers: {
+							Authorization: `Bearer ${GITHUB_TOKEN}`,
+						},
+					});
+					const { name, bio } = await res.json();
+					return {
+						...contributor,
+						name,
+						bio,
+					};
+				})
+			)) as FullContributor[];
+
+			storedContributors = contributorsWithName.filter(({ login }) => !login.includes('[bot]'));
+			// eslint-disable-next-line no-console
+			console.log(`Fetched ${storedContributors.length} contributors`);
+		} catch {
+			return [];
+		}
+	}
+
+	return storedContributors;
+};
+
+export const load: PageLoad = async () => {
+	return {
+		contributors: await getContributors(),
+	};
+};
diff --git a/src/routes/docs/[...slug]/+page.ts b/src/routes/docs/[...slug]/+page.ts
index d57b783f..dbdee300 100644
--- a/src/routes/docs/[...slug]/+page.ts
+++ b/src/routes/docs/[...slug]/+page.ts
@@ -15,5 +15,6 @@ export const load: PageLoad = async (event) => {
 		component: doc.default,
 		metadata: doc.metadata,
 		title: doc.metadata.title,
+		...event.data,
 	};
 };
diff --git a/static/logo-mark.svg b/static/logo-mark.svg
deleted file mode 100644
index 0e53e7e0..00000000
--- a/static/logo-mark.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<svg width="51" height="101" viewBox="0 0 51 101" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M34 72.6471H31C23.268 72.6471 17 78.9151 17 86.6471V92.1471C17 96.8411 20.8056 100.647 25.5 100.647C30.1944 100.647 34 96.8411 34 92.1471V72.6471Z" fill="#BB6F5D"/>
-<path d="M5 50.6471C9.4183 50.6471 13 54.2288 13 58.6471V75.6471C13 77.8562 11.2091 79.6471 9 79.6471C6.7909 79.6471 5 77.8562 5 75.6471V50.6471Z" fill="#F7B155"/>
-<path d="M0 45.6471C4.41828 45.6471 8 49.2288 8 53.6471V70.6471C8 72.8562 6.20914 74.6471 4 74.6471C1.79086 74.6471 0 72.8562 0 70.6471V45.6471Z" fill="#F7B155"/>
-<path d="M10 39.6471C14.4183 39.6471 18 43.2288 18 47.6471V64.6471C18 66.8562 16.2091 68.6471 14 68.6471C11.7909 68.6471 10 66.8562 10 64.6471V39.6471Z" fill="#F7B155"/>
-<path d="M23.0408 2.77659C23.1745 1.30114 22.1008 -0.228138 20.6417 0.0283519C8.9124 2.0901 0 12.3267 0 24.6471V68.6471H45C48.3137 68.6471 51 65.9608 51 62.6471V24.6471C51 23.757 50.0658 23.1954 49.1982 23.3943C48.4789 23.5592 47.7417 23.6444 47 23.6471C41.4772 23.6471 37 19.1699 37 13.6471C37.0033 13.2425 36.6105 12.9507 36.2287 13.0846C35.1927 13.4477 34.1019 13.6384 33 13.6471C27.4772 13.6471 23 9.16992 23 3.64712C23.0009 3.35597 23.0146 3.06553 23.0408 2.77659Z" fill="#F7B155"/>
-</svg>
diff --git a/static/logo.svg b/static/logo.svg
deleted file mode 100644
index 5cc15848..00000000
--- a/static/logo.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-<svg width="340" height="109" viewBox="0 0 340 109" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M37 79H34C26.268 79 20 85.268 20 93V98.5C20 103.194 23.8056 107 28.5 107C33.1944 107 37 103.194 37 98.5V79Z" fill="#BB6F5D"/>
-<path d="M8 57C12.4183 57 16 60.5817 16 65V82C16 84.2091 14.2091 86 12 86C9.7909 86 8 84.2091 8 82V57Z" fill="#F7B155"/>
-<path d="M3 52C7.41828 52 11 55.5817 11 60V77C11 79.2091 9.20914 81 7 81C4.79086 81 3 79.2091 3 77V52Z" fill="#F7B155"/>
-<path d="M13 46C17.4183 46 21 49.5817 21 54V71C21 73.2091 19.2091 75 17 75C14.7909 75 13 73.2091 13 71V46Z" fill="#F7B155"/>
-<path d="M26.0408 9.12947C26.1745 7.65402 25.1008 6.12474 23.6417 6.38123C11.9124 8.44298 3 18.6796 3 31V75H48C51.3137 75 54 72.3137 54 69V31V31C54 30.1099 53.0658 29.5483 52.1982 29.7472C51.4789 29.9121 50.7417 29.9973 50 30C44.4772 30 40 25.5228 40 20V20C40.0033 19.5954 39.6105 19.3036 39.2287 19.4375C38.1927 19.8006 37.1019 19.9913 36 20C30.4772 20 26 15.5228 26 10C26.0009 9.70885 26.0146 9.41841 26.0408 9.12947Z" fill="#F7B155"/>
-<path d="M103.25 77.75C101.854 77.75 100.688 77.2604 99.75 76.2812C98.8333 75.3021 98.375 74.0521 98.375 72.5312V37.3438C98.375 35.4688 98.9062 33.9896 99.9688 32.9062C101.031 31.8021 102.417 31.25 104.125 31.25C105.625 31.25 106.833 31.6146 107.75 32.3438C108.667 33.0729 109.604 34.3125 110.562 36.0625L122.812 58.8438L135.031 36.0625C135.99 34.3125 136.927 33.0729 137.844 32.3438C138.781 31.6146 139.99 31.25 141.469 31.25C143.177 31.25 144.562 31.8021 145.625 32.9062C146.708 33.9896 147.25 35.4688 147.25 37.3438V72.5312C147.25 74.0521 146.781 75.3021 145.844 76.2812C144.927 77.2604 143.76 77.75 142.344 77.75C140.927 77.75 139.76 77.25 138.844 76.25C137.927 75.25 137.469 74.0104 137.469 72.5312V50.1875L128.781 66.75C127.656 68.8125 126.698 70.2083 125.906 70.9375C125.115 71.6458 124.073 72 122.781 72C121.531 72 120.5 71.6458 119.688 70.9375C118.896 70.2083 117.948 68.8125 116.844 66.75L108.188 50.25V72.5312C108.188 74.0104 107.719 75.25 106.781 76.25C105.844 77.25 104.667 77.75 103.25 77.75ZM175.406 77.9375C169.719 77.9375 165.167 76.3021 161.75 73.0312C158.333 69.7396 156.625 65.3438 156.625 59.8438C156.625 54.5104 158.271 50.1562 161.562 46.7812C164.875 43.3854 169.125 41.6875 174.312 41.6875C179.312 41.6875 183.354 43.1771 186.438 46.1562C189.542 49.1146 191.094 52.9062 191.094 57.5312C191.094 59.3438 190.698 60.6771 189.906 61.5312C189.115 62.3646 187.885 62.7812 186.219 62.7812H166C166.333 64.8229 167.385 66.4375 169.156 67.625C170.927 68.7917 173.156 69.375 175.844 69.375C178.781 69.375 181.104 68.5521 182.812 66.9062C184.271 65.9479 185.594 65.4688 186.781 65.4688C189.115 65.4688 190.281 66.6979 190.281 69.1562C190.281 70.1979 189.865 71.2812 189.031 72.4062C187.802 74.1354 185.938 75.4896 183.438 76.4688C180.958 77.4479 178.281 77.9375 175.406 77.9375ZM166 55.5938H181.844C181.573 53.5521 180.74 51.9583 179.344 50.8125C177.948 49.6458 176.146 49.0625 173.938 49.0625C171.729 49.0625 169.917 49.6458 168.5 50.8125C167.104 51.9792 166.271 53.5729 166 55.5938ZM204.5 77.75C203.104 77.75 201.969 77.3021 201.094 76.4062C200.24 75.4896 199.812 74.3125 199.812 72.875V36.125C199.812 34.6875 200.24 33.5208 201.094 32.625C201.969 31.7083 203.104 31.25 204.5 31.25C205.875 31.25 206.99 31.7083 207.844 32.625C208.719 33.5208 209.156 34.6875 209.156 36.125V72.875C209.156 74.3125 208.719 75.4896 207.844 76.4062C206.99 77.3021 205.875 77.75 204.5 77.75ZM234.719 77.9375C231.49 77.9375 228.844 76.9271 226.781 74.9062C224.719 72.8854 223.688 70.2188 223.688 66.9062V51.0312H220.25C218.979 51.0312 217.927 50.6354 217.094 49.8438C216.281 49.0521 215.875 48.0417 215.875 46.8125C215.875 45.5833 216.281 44.5833 217.094 43.8125C217.906 43.0208 218.958 42.625 220.25 42.625H223.688V37.1875C223.688 35.75 224.125 34.5833 225 33.6875C225.875 32.7708 227 32.3125 228.375 32.3125C229.771 32.3125 230.896 32.7708 231.75 33.6875C232.625 34.5833 233.062 35.75 233.062 37.1875V42.625H240.75C242.042 42.625 243.094 43.0208 243.906 43.8125C244.719 44.5833 245.125 45.5833 245.125 46.8125C245.125 48.0417 244.708 49.0521 243.875 49.8438C243.062 50.6354 242.021 51.0312 240.75 51.0312H233.062V65.5C233.062 67.875 234.042 69.0625 236 69.0625C236.896 69.0625 237.979 68.5729 239.25 67.5938C240.271 66.8021 241.333 66.4062 242.438 66.4062C243.5 66.4062 244.365 66.75 245.031 67.4375C245.698 68.125 246.031 69.0208 246.031 70.125C246.031 71.8958 245.312 73.4375 243.875 74.75C242.792 75.7292 241.438 76.5 239.812 77.0625C238.188 77.6458 236.49 77.9375 234.719 77.9375ZM306.094 72.8438C302.615 76.2396 297.969 77.9375 292.156 77.9375C286.344 77.9375 281.688 76.2396 278.188 72.8438C274.708 69.4271 272.969 64.9062 272.969 59.2812V36.3438C272.969 34.8438 273.438 33.625 274.375 32.6875C275.312 31.7292 276.521 31.25 278 31.25C279.458 31.25 280.635 31.7292 281.531 32.6875C282.427 33.625 282.875 34.8438 282.875 36.3438V58.3438C282.875 61.2188 283.719 63.5417 285.406 65.3125C287.115 67.0833 289.365 67.9688 292.156 67.9688C294.969 67.9688 297.219 67.0833 298.906 65.3125C300.615 63.5417 301.469 61.2188 301.469 58.3438V36.3438C301.469 34.8438 301.917 33.625 302.812 32.6875C303.708 31.7292 304.885 31.25 306.344 31.25C307.823 31.25 309.021 31.7292 309.938 32.6875C310.875 33.625 311.344 34.8438 311.344 36.3438V59.2812C311.344 64.9062 309.594 69.4271 306.094 72.8438ZM328.312 77.75C326.833 77.75 325.635 77.2812 324.719 76.3438C323.823 75.4062 323.375 74.1771 323.375 72.6562V36.3438C323.375 34.8229 323.823 33.5938 324.719 32.6562C325.635 31.7188 326.833 31.25 328.312 31.25C329.812 31.25 331.01 31.7188 331.906 32.6562C332.823 33.5938 333.281 34.8229 333.281 36.3438V72.6562C333.281 74.1771 332.823 75.4062 331.906 76.3438C331.01 77.2812 329.812 77.75 328.312 77.75Z" fill="white"/>
-</svg>
diff --git a/static/logo_mark.svg b/static/logo_mark.svg
new file mode 100644
index 00000000..c3ce3cd3
--- /dev/null
+++ b/static/logo_mark.svg
@@ -0,0 +1,12 @@
+<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_375_141)">
+<path d="M72.5 89.7227C72.5 88.0658 71.1569 86.7227 69.5 86.7227V86.7227C61.768 86.7227 55.5 93.0527 55.5 100.861V106.416C55.5 111.156 59.3056 115 64 115C68.6944 115 72.5 111.156 72.5 106.416V89.7227Z" fill="#BB6F5D"/>
+<path d="M38.5 76.6831C38.5 79.9968 41.1863 82.6831 44.5 82.6831H83.5C86.8137 82.6831 89.5 79.9968 89.5 76.6831V48.3465L63.5 32.693V32.693C53.1872 24.986 38.5 32.3454 38.5 45.2198V76.6831Z" fill="#72470D"/>
+<path d="M89.5 40.5C89.5 26.4167 78.0833 15 64 15V15C49.9167 15 38.5 26.4167 38.5 40.5V79.8136C38.5 82.678 40.822 85 43.6864 85V85C46.5508 85 48.8729 82.678 48.8729 79.8136V65.9703C48.8729 63.2253 51.0982 61 53.8432 61V61C56.5883 61 58.8136 63.2253 58.8136 65.9703V73.4306C58.8136 76.295 61.1356 78.617 64 78.617H64.5375C67.105 78.617 69.1864 76.5356 69.1864 73.9681V73.6101C69.1864 71.2403 71.1076 69.3191 73.4774 69.3191H73.978C76.1086 69.3191 78.0919 70.4065 79.2377 72.2028V72.2028C79.8506 73.1636 80.8679 73.7929 82.0014 73.9123L83.0189 74.0195C86.4829 74.3843 89.5 71.6683 89.5 68.1852V40.5Z" fill="#F7B155"/>
+</g>
+<defs>
+<clipPath id="clip0_375_141">
+<rect width="51" height="102" fill="white" transform="translate(38.5 13)"/>
+</clipPath>
+</defs>
+</svg>


Test patch:
diff --git a/src/tests/tooltip/Tooltip.spec.ts b/src/tests/tooltip/Tooltip.spec.ts
index 76ad53ea..c4b99025 100644
--- a/src/tests/tooltip/Tooltip.spec.ts
+++ b/src/tests/tooltip/Tooltip.spec.ts
@@ -197,3 +197,18 @@ describe('2 tooltips, group=undefined', () => {
 		expect(get(open2)).toBe(true);
 	});
 });
+
+describe('Single tooltip, group=true', () => {
+	test('Tooltip does not prevent itself from opening', () => {
+		const open = writable(true);
+		render(Tooltip, {
+			open,
+			group: true,
+		});
+
+		open.set(false);
+
+		open.set(true);
+		expect(get(open)).toBe(true);
+	});
+});