diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 505586807829825cbf77c95e35f4af643f297f8d..d51dd8c6c900093fb7cccbfa52c1b879277ce1ff 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -28,34 +28,27 @@
     <codeStyleSettings language="HTML">
       <option name="SOFT_MARGINS" value="100" />
       <indentOptions>
-        <option name="INDENT_SIZE" value="2" />
-        <option name="CONTINUATION_INDENT_SIZE" value="2" />
-        <option name="TAB_SIZE" value="2" />
+        <option name="CONTINUATION_INDENT_SIZE" value="4" />
         <option name="USE_TAB_CHARACTER" value="true" />
       </indentOptions>
     </codeStyleSettings>
     <codeStyleSettings language="JavaScript">
       <option name="SOFT_MARGINS" value="100" />
       <indentOptions>
-        <option name="INDENT_SIZE" value="2" />
-        <option name="CONTINUATION_INDENT_SIZE" value="2" />
-        <option name="TAB_SIZE" value="2" />
         <option name="USE_TAB_CHARACTER" value="true" />
       </indentOptions>
     </codeStyleSettings>
     <codeStyleSettings language="TypeScript">
       <option name="SOFT_MARGINS" value="100" />
       <indentOptions>
-        <option name="INDENT_SIZE" value="2" />
-        <option name="CONTINUATION_INDENT_SIZE" value="2" />
-        <option name="TAB_SIZE" value="2" />
         <option name="USE_TAB_CHARACTER" value="true" />
       </indentOptions>
     </codeStyleSettings>
     <codeStyleSettings language="Vue">
       <option name="SOFT_MARGINS" value="100" />
       <indentOptions>
-        <option name="CONTINUATION_INDENT_SIZE" value="2" />
+        <option name="INDENT_SIZE" value="4" />
+        <option name="TAB_SIZE" value="4" />
         <option name="USE_TAB_CHARACTER" value="true" />
       </indentOptions>
     </codeStyleSettings>
diff --git a/.prettierrc b/.prettierrc
index 3f7802c3728f1ae67e245f68735362eb8fa4fe4b..a3896e01760b9b7bb8f1c4f614496e8875c910a9 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,8 +1,11 @@
 {
 	"useTabs": true,
+	"tabWidth": 4,
 	"singleQuote": true,
 	"trailingComma": "none",
 	"printWidth": 100,
+	"bracketSameLine": true,
+
 	"plugins": ["prettier-plugin-svelte"],
 	"overrides": [
 		{
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
index 6455613828f9fb19cf5bc3cf9684392c495e0e24..dbd1c7ac230a22cec028535e7fc084e94c432e6d 100644
--- a/.storybook/preview.ts
+++ b/.storybook/preview.ts
@@ -8,7 +8,7 @@ const preview: Preview = {
 				date: /Date$/i
 			}
 		}
-	},
+	}
 };
 
 export default preview;
diff --git a/package.json b/package.json
index b5728df8008f6f7f97aa126b305ffd5eac9be306..e70aa0acd82e7394f92c06fd92ba28b56f6f80e2 100644
--- a/package.json
+++ b/package.json
@@ -22,10 +22,12 @@
 		"!dist/**/*.test.*",
 		"!dist/**/*.spec.*",
 		"!dist/**/*.stories.*",
+		"!dist/**/*.internal.*",
 		"src/lib",
 		"!src/lib/**/*.test.*",
 		"!src/lib/**/*.spec.*",
-		"!src/lib/**/*.stories.*"
+		"!src/lib/**/*.stories.*",
+		"!src/lib/**/*.internal.*"
 	],
 	"sideEffects": [
 		"**/*.css"
@@ -40,6 +42,9 @@
 		},
 		"./barbi.css": {
 			"default": "./dist/barbi.css"
+		},
+		"./icons": {
+			"default": "./dist/icons.js"
 		}
 	},
 	"peerDependencies": {
diff --git a/src/lib/atoms/Button.stories.svelte b/src/lib/atoms/Button.stories.svelte
index 9f2fcd921ad0683863a1d1af8a4b93ff7d1917f6..6ca5f3a430ddacbca38124f166490536558c23b2 100644
--- a/src/lib/atoms/Button.stories.svelte
+++ b/src/lib/atoms/Button.stories.svelte
@@ -4,7 +4,7 @@
 
 	const { Story } = defineMeta({
 		title: 'Components/Atoms/Button',
-		component: Button,
+		component: Button
 	});
 </script>
 
@@ -45,17 +45,20 @@
 <Story name="Invisible Button">
 	{#snippet children(args)}
 		<BarbiTheme />
-		<p>There is a button on this page, but you can't see it. Use the `placeholder` prop to create a button that occupies only vertical space,
-			with no interaction available. Useful for reserving space.</p>
+		<p>
+			There is a button on this page, but you can't see it. Use the `placeholder` prop to
+			create a button that occupies only vertical space, with no interaction available. Useful
+			for reserving space.
+		</p>
 		<Button placeholder {...args}>You aren't able to see this button at all!</Button>
 	{/snippet}
 </Story>
 
 <style>
 	.storyboard-grid {
-			display: grid;
-			grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
-			grid-auto-rows: max-content;
-			gap: var(--spacing-md);
+		display: grid;
+		grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+		grid-auto-rows: max-content;
+		gap: var(--spacing-md);
 	}
 </style>
diff --git a/src/lib/atoms/Button.svelte b/src/lib/atoms/Button.svelte
index c63b7c2706c8add626730bdee228c912006e9670..8fccc59219b65579617d3fad38d82fc5a59da8c5 100644
--- a/src/lib/atoms/Button.svelte
+++ b/src/lib/atoms/Button.svelte
@@ -1,79 +1,91 @@
-<script lang="ts">
-	import type { HTMLButtonAttributes } from 'svelte/elements'
-	import Panel from './Panel.svelte';
-
-	export type Props = {
-		children?: HTMLButtonAttributes['children'],
-		variant?: 'primary' | 'secondary' | 'success' | 'error'
-	}
+<script module lang="ts">
+	import type { Props as PanelProps } from './Panel.svelte';
+	import type { Snippet } from 'svelte';
 
-	const { children, variant, placeholder, ...props }: Props = $props()
-	const tag = $derived(props.href != null ? 'a' : 'button')
+	export type Props = Omit<PanelProps, 'variant'> & {
+		variant?: 'primary' | 'secondary' | 'success' | 'error';
+		children?: Snippet;
+		href?: string;
+	};
+</script>
 
+<script lang="ts">
+	import Panel from './Panel.svelte';
+	const { children, variant, placeholder, ...props }: Props = $props();
+	const tag = $derived(props.href != null ? 'a' : 'button');
 </script>
 
-<Panel class={['barbi-button', `barbi-button-${variant}`, !!placeholder && 'placeholder']} space="sm" {...props} tag={tag} hover active onclick={() => alert("foo")}>
+<Panel
+	class={['barbi-button', `barbi-button-${variant}`, !!placeholder && 'placeholder']}
+	space="sm"
+	{...props}
+	{tag}
+	hover
+	active
+>
 	{@render children?.()}
 </Panel>
 
 <style>
 	:global {
-			a.panel.barbi-button,
-			.panel.barbi-button a {
-					text-decoration: none;
-      }
-			.panel.barbi-button {
-					cursor: pointer;
-					--component-background: var(--colour-primary);
-			}
-
-			.panel.barbi-button.placeholder {
-					cursor: inherit;
-					pointer-events: none !important;
-					width: 0 !important;
-					opacity: 0 !important;
-			}
+		a.panel.barbi-button,
+		.panel.barbi-button a {
+			text-decoration: none;
+		}
+		.panel.barbi-button {
+			cursor: pointer;
+			display: inline-flex;
+			flex-direction: row;
+			align-items: center;
+			--component-background: var(--colour-primary);
+		}
 
-			.panel.barbi-button:disabled {
-					cursor: not-allowed;
-					--component-background: var(--colour-concrete);
-					--component-colour: var(--colour-slate);
-          --border-colour: var(--colour-steel);
-					--colour-shadow: var(--colour-steel);
-			}
+		.panel.barbi-button.placeholder {
+			cursor: inherit;
+			pointer-events: none !important;
+			width: 0 !important;
+			opacity: 0 !important;
+		}
 
-			.panel.barbi-button:hover:not(:disabled) {
-					cursor: pointer;
-					--component-background: var(--colour-secondary);
-			}
+		.panel.barbi-button:disabled {
+			cursor: not-allowed;
+			--component-background: var(--colour-concrete);
+			--component-colour: var(--colour-slate);
+			--border-colour: var(--colour-steel);
+			--colour-shadow: var(--colour-steel);
+		}
 
-			.panel.barbi-button.barbi-button-primary:not(:disabled) {
-					--component-background: var(--colour-primary);
-			}
-      .panel.barbi-button.barbi-button-primary:hover:not(:disabled) {
-					--component-background: var(--colour-secondary);
-      }
+		.panel.barbi-button:hover:not(:disabled) {
+			cursor: pointer;
+			--component-background: var(--colour-secondary);
+		}
 
-			.panel.barbi-button.barbi-button-secondary:not(:disabled) {
-					--component-background: var(--colour-emphasis);
-			}
-      .panel.barbi-button.barbi-button-secondary:hover:not(:disabled) {
-					--component-background: var(--colour-mustard);
-      }
+		.panel.barbi-button.barbi-button-primary:not(:disabled) {
+			--component-background: var(--colour-primary);
+		}
+		.panel.barbi-button.barbi-button-primary:hover:not(:disabled) {
+			--component-background: var(--colour-secondary);
+		}
 
-			.panel.barbi-button.barbi-button-success:not(:disabled) {
-					--component-background: var(--colour-success);
-			}
-      .panel.barbi-button.barbi-button-success:hover:not(:disabled) {
-					--component-background: var(--colour-pastel);
-      }
+		.panel.barbi-button.barbi-button-secondary:not(:disabled) {
+			--component-background: var(--colour-emphasis);
+		}
+		.panel.barbi-button.barbi-button-secondary:hover:not(:disabled) {
+			--component-background: var(--colour-mustard);
+		}
 
-			.panel.barbi-button.barbi-button-error:not(:disabled) {
-					--component-background: var(--colour-error);
-			}
-      .panel.barbi-button.barbi-button-error:hover:not(:disabled) {
-					--component-background: var(--colour-mustard);
-      }
+		.panel.barbi-button.barbi-button-success:not(:disabled) {
+			--component-background: var(--colour-success);
+		}
+		.panel.barbi-button.barbi-button-success:hover:not(:disabled) {
+			--component-background: var(--colour-pastel);
+		}
 
+		.panel.barbi-button.barbi-button-error:not(:disabled) {
+			--component-background: var(--colour-error);
+		}
+		.panel.barbi-button.barbi-button-error:hover:not(:disabled) {
+			--component-background: var(--colour-mustard);
+		}
 	}
-</style>
\ No newline at end of file
+</style>
diff --git a/src/lib/atoms/Panel.stories.svelte b/src/lib/atoms/Panel.stories.svelte
index 6592f6d65788d5d942dfc08e63a525dd829c8aca..163d3a4f443f354272de8b5901b52b51a8ae7b82 100644
--- a/src/lib/atoms/Panel.stories.svelte
+++ b/src/lib/atoms/Panel.stories.svelte
@@ -4,7 +4,7 @@
 
 	const { Story } = defineMeta({
 		title: 'Components/Atoms/Panel',
-		component: Panel,
+		component: Panel
 	});
 </script>
 
@@ -14,30 +14,18 @@
 <Story name="Simple Panel">
 	{#snippet children(args)}
 		<BarbiTheme />
-		<Panel {...args}>
-			The most basic panel type
-		</Panel>
-		<Panel flat {...args}>
-			A basic panel without shadow
-		</Panel>
+		<Panel {...args}>The most basic panel type</Panel>
+		<Panel flat {...args}>A basic panel without shadow</Panel>
 	{/snippet}
 </Story>
 
 <Story name="Sizable Panel">
 	{#snippet children(args)}
 		<BarbiTheme />
-		<Panel space="none" {...args}>
-			space="none"
-		</Panel>
-		<Panel space="sm" {...args}>
-			space="sm"
-		</Panel>
-		<Panel space="md" {...args}>
-			space="md"
-		</Panel>
-		<Panel space="lg" {...args}>
-			space="lg"
-		</Panel>
+		<Panel space="none" {...args}>space="none"</Panel>
+		<Panel space="sm" {...args}>space="sm"</Panel>
+		<Panel space="md" {...args}>space="md"</Panel>
+		<Panel space="lg" {...args}>space="lg"</Panel>
 	{/snippet}
 </Story>
 
@@ -45,36 +33,16 @@
 	{#snippet children(args)}
 		<BarbiTheme />
 		<div class="storyboard-grid">
-			<Panel variant="primary" {...args}>
-				variant="primary"
-			</Panel>
-			<Panel variant="secondary" {...args}>
-				variant="secondary"
-			</Panel>
-			<Panel variant="success" {...args}>
-				variant="success"
-			</Panel>
-			<Panel variant="error" {...args}>
-				variant="error"
-			</Panel>
-			<Panel variant="emphasis" {...args}>
-				variant="emphasis"
-			</Panel>
-			<Panel variant="mustard" {...args}>
-				variant="mustard"
-			</Panel>
-			<Panel variant="pastel" {...args}>
-				variant="pastel"
-			</Panel>
-			<Panel variant="brick" {...args}>
-				variant="brick"
-			</Panel>
-			<Panel variant="neon" {...args}>
-				variant="neon"
-			</Panel>
-			<Panel variant="slate" {...args}>
-				variant="slate"
-			</Panel>
+			<Panel variant="primary" {...args}>variant="primary"</Panel>
+			<Panel variant="secondary" {...args}>variant="secondary"</Panel>
+			<Panel variant="success" {...args}>variant="success"</Panel>
+			<Panel variant="error" {...args}>variant="error"</Panel>
+			<Panel variant="emphasis" {...args}>variant="emphasis"</Panel>
+			<Panel variant="mustard" {...args}>variant="mustard"</Panel>
+			<Panel variant="pastel" {...args}>variant="pastel"</Panel>
+			<Panel variant="brick" {...args}>variant="brick"</Panel>
+			<Panel variant="neon" {...args}>variant="neon"</Panel>
+			<Panel variant="slate" {...args}>variant="slate"</Panel>
 		</div>
 	{/snippet}
 </Story>
@@ -92,20 +60,22 @@
 	{#snippet children(args)}
 		<BarbiTheme />
 		<Panel {...args} hover active>
-			hover={true} active={true}
+			hover={true} active={true} (regular panel)
 		</Panel>
 		<Panel {...args} tag="button" hover active>
-			hover={true} active={true}
+			hover={true} active={true} (button tag panel)
+		</Panel>
+		<Panel {...args} tag="code" hover active>
+			hover={true} active={true} (code tag panel)
 		</Panel>
 	{/snippet}
 </Story>
 
-
 <style>
 	.storyboard-grid {
-			display: grid;
-			grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
-			grid-auto-rows: max-content;
-			gap: var(--spacing-md);
+		display: grid;
+		grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+		grid-auto-rows: max-content;
+		gap: var(--spacing-md);
 	}
 </style>
diff --git a/src/lib/atoms/Panel.svelte b/src/lib/atoms/Panel.svelte
index ec0e206af84ec945275d7359663190d861110e92..2483a0e762b1dc71cc3eff872dbfd89eecb2d448 100644
--- a/src/lib/atoms/Panel.svelte
+++ b/src/lib/atoms/Panel.svelte
@@ -28,8 +28,9 @@
 		flat = false,
 		class: className,
 		children = null,
+		shadowMargin = true,
 		...rest
-	}: Props<Tag> = $props();
+	}: Props = $props();
 </script>
 
 <svelte:element
@@ -41,6 +42,7 @@
 			'shadow-sm': shadow === 'sm',
 			'shadow-md': shadow === 'md',
 			'shadow-lg': shadow === 'lg',
+			'shadow-margin': shadowMargin,
 			hover,
 			active,
 			full,
@@ -76,6 +78,11 @@
 		box-shadow: var(--shadow-md);
 	}
 
+	.panel:not(.flat).shadow-margin {
+		margin-right: var(--spacing-sm);
+		margin-bottom: var(--spacing-sm);
+	}
+
 	.panel.primary {
 		--component-background: var(--colour-primary);
 	}
diff --git a/src/lib/atoms/popover/Popover.stories.svelte b/src/lib/atoms/popover/Popover.stories.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..61b8615d4a1cf24536c9b2dc716e9c0deda291e9
--- /dev/null
+++ b/src/lib/atoms/popover/Popover.stories.svelte
@@ -0,0 +1,65 @@
+<script module>
+	import { Anchor, BarbiTheme, Popover } from '$lib';
+	import { defineMeta } from '@storybook/addon-svelte-csf';
+
+	const { Story } = defineMeta({
+		title: 'Components/Atoms/Popover',
+		component: Popover,
+		argTypes: {
+			position: {
+				name: 'Anchor Position',
+				control: 'radio',
+				options: [
+					Anchor.TopLeft,
+					Anchor.TopCenter,
+					Anchor.TopRight,
+					Anchor.BottomLeft,
+					Anchor.BottomCenter,
+					Anchor.BottomRight
+				]
+			}
+		},
+		args: {
+			position: Anchor.BottomRight,
+			spaceHorizontal: '0rem',
+			spaceVertical: 'var(--spacing-sm)'
+		}
+	});
+</script>
+
+<script>
+	import { Panel } from '$lib';
+</script>
+
+<Story name="Simple Popover">
+	{#snippet children(args)}
+		<BarbiTheme />
+		<div class="storybook-center">
+			<Popover {...args}>
+				<div class="content-wrapper">This is the trigger, click me</div>
+
+				{#snippet popover()}
+					<Panel>This element contains a popover</Panel>
+				{/snippet}
+			</Popover>
+		</div>
+	{/snippet}
+</Story>
+
+<style>
+	.storybook-center {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		height: 100vh;
+	}
+
+	.content-wrapper {
+		border: 1px solid black;
+		padding: 2px 4px;
+	}
+
+	:global(.sb-main-padded) {
+		padding: 0 !important;
+	}
+</style>
diff --git a/src/lib/atoms/popover/Popover.svelte b/src/lib/atoms/popover/Popover.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..156f8bb4bb04f25176d8bb14a646c41d08829ac9
--- /dev/null
+++ b/src/lib/atoms/popover/Popover.svelte
@@ -0,0 +1,126 @@
+<script module lang="ts">
+	import type { Snippet } from 'svelte';
+	import type { BarbiClass } from '../../utilTypes.js';
+	import type { AnchorPoint } from './utils.ts';
+
+	export type Props = {
+		children?: Snippet;
+		popover?: Snippet;
+		wrapperClass?: BarbiClass;
+		class?: BarbiClass;
+		position?: AnchorPoint;
+		ontoggle?: () => void;
+		enabled?: boolean;
+		style?: string;
+		spaceVertical?: string;
+		spaceHorizontal?: string;
+		open?: boolean;
+	};
+</script>
+
+<script lang="ts">
+	import { Anchor, calculatePosition } from './utils.ts';
+
+	let {
+		children,
+		wrapperClass,
+		class: className,
+		popover,
+		ontoggle,
+		enabled = true,
+		style,
+		position = Anchor.BottomLeft,
+		spaceVertical = 'var(--spacing-sm)',
+		spaceHorizontal = '0rem',
+		open = $bindable(false)
+	}: Props = $props();
+
+	let triggerElement = $state.raw(null);
+	let dialogElement = $state.raw(null);
+	// let open = $state(false);
+
+	$effect(() => {
+		if (!dialogElement) return;
+		if (enabled && open) {
+			dialogElement.showModal();
+			requestAnimationFrame(reposition);
+		} else {
+			dialogElement.close();
+		}
+		if (enabled && ontoggle) {
+			ontoggle(open);
+		}
+	});
+
+	function openPopover() {
+		if (enabled) {
+			open = true;
+		}
+	}
+
+	function onClickOut(event) {
+		if (
+			open &&
+			!dialogElement.contains(event.target) &&
+			!triggerElement.contains(event.target)
+		) {
+			open = false;
+		}
+	}
+
+	function reposition() {
+		if (!triggerElement || !dialogElement) return;
+		calculatePosition(position, triggerElement, dialogElement);
+	}
+</script>
+
+<svelte:window onclick={onClickOut} onresize={reposition} />
+
+<div
+	class={['popover-wrapper', wrapperClass]}
+	style={`${style ? style : ''}; --component-gap-vertical: ${spaceVertical}; --component-gap-horizontal: ${spaceHorizontal};`}
+>
+	<div bind:this={triggerElement} onclick={openPopover}>
+		{@render children?.()}
+	</div>
+	<dialog bind:this={dialogElement} class={[className, 'popover', { open }]}>
+		{@render popover?.()}
+	</dialog>
+</div>
+
+<style>
+	.popover-wrapper {
+		--component-gap-vertical: var(--spacing-md);
+		--component-gap-horizontal: var(--spacing-md);
+		display: inline-block;
+		position: relative;
+	}
+
+	.popover {
+		position: fixed;
+		border: none;
+		background-color: transparent;
+		z-index: 500;
+		outline: none;
+	}
+
+	.popover.open {
+		animation: popopen var(--transition-medium) forwards;
+	}
+
+	@keyframes popopen {
+		from {
+			opacity: 0;
+			transform: scale(0.95);
+		}
+		to {
+			opacity: 1;
+			transform: scale(1);
+		}
+	}
+
+	.popover::backdrop {
+		display: none;
+		background-color: transparent;
+	}
+</style>
diff --git a/src/lib/atoms/popover/utils.ts b/src/lib/atoms/popover/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..813f068a6e18c811a96b8d5cc7d9ec6050536010
--- /dev/null
+++ b/src/lib/atoms/popover/utils.ts
@@ -0,0 +1,94 @@
+export const Anchor = {
+	TopLeft: 'TopLeft',
+	TopCenter: 'TopCenter',
+	TopRight: 'TopRight',
+	BottomLeft: 'BottomLeft',
+	BottomCenter: 'BottomCenter',
+	BottomRight: 'BottomRight'
+} as const;
+
+export type AnchorType = typeof Anchor;
+export type AnchorName = keyof AnchorType;
+export type AnchorPoint = AnchorType[AnchorName];
+
+function offsetStyle(pixels: number, varType: string, sign: number): string {
+	const signValue = sign > 0 ? '+' : '-';
+	return `calc(${pixels}px ${signValue} var(--component-gap-${varType}))`;
+}
+
+export function calculatePosition(
+	anchor: AnchorPoint,
+	trigger: HTMLDivElement,
+	dialog: HTMLDivElement
+) {
+	const triggerRect = trigger.getBoundingClientRect();
+	const dialogRect = dialog.getBoundingClientRect();
+
+	let left: number;
+	let top: number;
+	let vertSign = 1;
+	let horizSign = 1;
+
+	switch (anchor) {
+		case Anchor.TopLeft:
+			left = triggerRect.left;
+			top = triggerRect.top - dialogRect.height;
+			horizSign = 1;
+			vertSign = -1;
+			break;
+		case Anchor.TopCenter:
+			left = triggerRect.left + triggerRect.width / 2 - dialogRect.width / 2;
+			top = triggerRect.top - dialogRect.height;
+			horizSign = 0;
+			vertSign = -1;
+			break;
+		case Anchor.TopRight:
+			left = triggerRect.right - dialogRect.width;
+			top = triggerRect.top - dialogRect.height;
+			horizSign = -1;
+			vertSign = -1;
+
+			break;
+		case Anchor.BottomLeft:
+			left = triggerRect.left;
+			top = triggerRect.bottom;
+			horizSign = 1;
+			vertSign = 1;
+
+			break;
+		case Anchor.BottomCenter:
+			left = triggerRect.left + triggerRect.width / 2 - dialogRect.width / 2;
+			top = triggerRect.bottom;
+			horizSign = 0;
+			vertSign = 1;
+			break;
+		case Anchor.BottomRight:
+			left = triggerRect.right - dialogRect.width;
+			top = triggerRect.bottom;
+			horizSign = -1;
+			vertSign = 1;
+			break;
+	}
+
+	// Adjust for window boundaries
+	if (left < 0) left = 0;
+	if (left + dialogRect.width > window.innerWidth) {
+		left = window.innerWidth - dialogRect.width;
+	}
+	if (top < 0) top = 0;
+	if (top + dialogRect.height > window.innerHeight) {
+		top = window.innerHeight - dialogRect.height;
+	}
+
+	if (vertSign) {
+		dialog.style.top = offsetStyle(top, 'vertical', vertSign);
+	} else {
+		dialog.style.top = `${top}px`;
+	}
+
+	if (horizSign) {
+		dialog.style.left = offsetStyle(left, 'horizontal', horizSign);
+	} else {
+		dialog.style.left = `${left}px`;
+	}
+}
diff --git a/src/lib/components/modal/CustomIcon.internal.svelte b/src/lib/components/modal/CustomIcon.internal.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..ebe5de82666d85b875c77398e03c7ad86cd60c90
--- /dev/null
+++ b/src/lib/components/modal/CustomIcon.internal.svelte
@@ -0,0 +1,8 @@
+<script>
+	const { class: className, ...props } = $props();
+</script>
+
+<svg viewBox="0 0 200 110" xmlns="http://www.w3.org/2000/svg"
+	 class={['icon', className]} {...props}>
+	<path d="m1.636 5.368 196.73 1.5-97.764 97.764-98.964-99.264z" fill="var(--colour-icon)"/>
+</svg>
diff --git a/src/lib/components/modal/Dropdown.stories.svelte b/src/lib/components/modal/Dropdown.stories.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..81e21295459d625cc273fbcc83299d0250a78ba9
--- /dev/null
+++ b/src/lib/components/modal/Dropdown.stories.svelte
@@ -0,0 +1,139 @@
+<script module>
+	import { Anchor, BarbiTheme, Dropdown } from '$lib';
+	import { defineMeta } from '@storybook/addon-svelte-csf';
+	import CustomIcon from './CustomIcon.internal.svelte';
+
+	const { Story } = defineMeta({
+		title: 'Components/Components/Dropdown',
+		component: Dropdown,
+		argTypes: {
+			alternate: {
+				name: 'Alternate Style',
+				control: 'boolean',
+			},
+			flat: {
+				name: 'Flat Style',
+				control: 'boolean',
+			},
+			options: {
+				table: {
+					disable: true
+				}
+			}
+		}
+	});
+</script>
+
+<Story
+	name="Simple Dropdown"
+	args={{
+		label: "I'm a dropdown",
+		options: [
+			{ value: '1', label: 'I log to the console', onClick() { console.log("First button") } },
+			{ value: '2', label: "I'm an external link", href: 'https://example.com' },
+		]
+	}}
+>
+	{#snippet children(args)}
+		<BarbiTheme />
+		<div class="storybook-center">
+			<Dropdown {...args} />
+		</div>
+	{/snippet}
+</Story>
+
+<Story
+	name="Alternate Colour Dropdown"
+	args={{
+		label: "This dropdown uses variant='secondary'",
+		alternate: true,
+		options: [
+			{ value: '1', label: 'I log to the console', onClick() { console.log("First button") } },
+			{ value: '2', label: "I'm an external link", href: 'https://example.com' },
+		]
+	}}
+>
+	{#snippet children(args)}
+		<BarbiTheme />
+		<div class="storybook-center">
+			<Dropdown {...args} />
+		</div>
+	{/snippet}
+</Story>
+
+<Story
+	name="Flat Style Dropdown"
+	args={{
+		label: "I'm Flat!",
+		flat: true,
+		options: [
+			{ value: '1', label: 'I log to the console', onClick() { console.log("First button") } },
+			{ value: '2', label: "I'm an external link", href: 'https://example.com' },
+		]
+	}}
+>
+	{#snippet children(args)}
+		<BarbiTheme />
+		<div class="storybook-center">
+			<Dropdown {...args} />
+		</div>
+	{/snippet}
+</Story>
+
+<Story
+	name="Only Icon"
+	args={{
+		label: "",
+		options: [
+			{ value: '1', label: 'I log to the console', onClick() { console.log("First button") } },
+			{ value: '2', label: "I'm an external link", href: 'https://example.com' },
+		]
+	}}
+>
+	{#snippet children(args)}
+		<BarbiTheme />
+		<div class="storybook-center">
+			<Dropdown {...args} />
+			<span>Creating a dropdown with an empty label will only render the icon</span>
+		</div>
+	{/snippet}
+</Story>
+
+<Story
+	name="With Custom Icon"
+	args={{
+		label: "It won't spin",
+		icon: CustomIcon,
+		options: [
+			{ value: '1', label: 'I log to the console', onClick() { console.log("First button") } },
+			{ value: '2', label: "I'm an external link", href: 'https://example.com' },
+		]
+	}}
+>
+	{#snippet children(args)}
+		<BarbiTheme />
+		<div class="storybook-center">
+			<Dropdown {...args} />
+			<span>Pass a component to the 'icon' prop that uses the global '.icon' class, and merges it with a 'class' property</span>
+		</div>
+	{/snippet}
+</Story>
+
+<style>
+	.storybook-center {
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		height: 100vh;
+	}
+
+	.storybook-center span {
+		max-width: 50%;
+		margin: 10px 0;
+	}
+
+	:global(.sb-main-padded) {
+		padding: 0 !important;
+	}
+</style>
diff --git a/src/lib/components/modal/Dropdown.svelte b/src/lib/components/modal/Dropdown.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..6ab9d745de6f4508846929b0b6601f04071784ca
--- /dev/null
+++ b/src/lib/components/modal/Dropdown.svelte
@@ -0,0 +1,92 @@
+<script lang="ts" module>
+	import type { Component } from 'svelte';
+
+	export type Props = {
+		label?: string;
+		options?: DropdownOption[];
+		alternate?: boolean,
+		flat?: boolean,
+		icon?: Component<any>
+	};
+
+	export type DropdownOption = {
+		label: string;
+		value: string;
+		onClick?: () => void;
+		href?: () => void;
+	};
+</script>
+
+<script lang="ts">
+	import Button from '../../atoms/Button.svelte';
+	import Panel from '../../atoms/Panel.svelte';
+	import Popover from '../../atoms/popover/Popover.svelte';
+	import AngleDownIcon from '../..//patterns/icons/AngleDownIcon.svelte';
+
+	const { label = '', options = [], alternate = false, flat = false, icon: Icon = AngleDownIcon }: Props = $props();
+	let open = $state(false);
+	let shouldAnimate = $derived.by(() => Icon === AngleDownIcon)
+
+	function wrapOnClick(option: DropdownOption) {
+		if (option.onClick) {
+			const onClick = option.onClick;
+			return (...args: any[]) => {
+				open = false;
+				return onClick(...args);
+			};
+		} else if (option.href) {
+			return () => {
+				open = false;
+			};
+		}
+	}
+</script>
+
+<Popover bind:open>
+	<Button variant={alternate ? 'secondary' : 'primary'} flat={flat}>
+		<span class="dropdown-label">{label}</span>
+		<Icon style="--size-icon: 1.25rem;" class={['dropdown-arrow', shouldAnimate && 'dropdown-arrow-animate', { open }]} />
+	</Button>
+
+	{#snippet popover()}
+		<Panel space="none" class="dropdown-popover-list" flat={flat}>
+			{#each options as option}
+				<Button variant={alternate ? 'success' : 'secondary'} flat onclick={wrapOnClick(option)} href={option.href}>
+					{option.label}
+				</Button>
+			{/each}
+		</Panel>
+	{/snippet}
+</Popover>
+
+<style>
+	:global {
+		.dropdown-arrow {
+			margin-left: var(--spacing-md);
+			transition: all var(--transition-fast);
+			transform: rotate3d(0, 0, 1, 0deg);
+		}
+
+		.dropdown-arrow.dropdown-arrow-animate.open {
+			transform: rotate3d(0, 0, 1, 180deg);
+		}
+
+		.panel.dropdown-popover-list {
+			display: flex;
+			flex-direction: column;
+			align-items: stretch;
+			justify-content: end;
+		}
+		.panel.dropdown-popover-list .barbi-button {
+			border-width: 0;
+			border-bottom-width: var(--border-width);
+		}
+		.panel.dropdown-popover-list .barbi-button:last-child {
+			border-width: 0;
+		}
+	}
+
+	.dropdown-label:empty + :global(.dropdown-arrow) {
+		margin-left: 0;
+	}
+</style>
diff --git a/src/lib/icons.ts b/src/lib/icons.ts
new file mode 100644
index 0000000000000000000000000000000000000000..272d1aaa97128789dd51945ac718310a9b782279
--- /dev/null
+++ b/src/lib/icons.ts
@@ -0,0 +1,3 @@
+import ArrowDownIcon from './patterns/icons/AngleDownIcon.svelte';
+
+export { ArrowDownIcon };
diff --git a/src/lib/index.ts b/src/lib/index.ts
index 3ffd12aac5dc7d9c3967891a5d48a1cddd7705eb..49856ce060898e7e586db600e46d9483d114085f 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -2,5 +2,22 @@ import BarbiTheme from './theme/BarbiTheme.svelte';
 import Panel from './atoms/Panel.svelte';
 import Button from './atoms/Button.svelte';
 import CheckerboardPattern from './patterns/CheckerboardPattern.svelte';
+import Popover from './atoms/popover/Popover.svelte';
+import { Anchor } from './atoms/popover/utils.js';
+import Dropdown from './components/modal/Dropdown.svelte';
 
-export { BarbiTheme, CheckerboardPattern, Panel, Button };
+import type { AnchorName } from './atoms/popover/utils.ts';
+
+// Utilities
+export { BarbiTheme, CheckerboardPattern, Anchor };
+
+// Atoms
+export {
+	Panel, Button, Popover,
+}
+
+// Components
+export { Dropdown };
+
+// Types
+export type { AnchorName };
diff --git a/src/lib/patterns/CheckerboardPattern.stories.svelte b/src/lib/patterns/CheckerboardPattern.stories.svelte
index e2ef5a266352dbd835c36cb5d65bf28f20d6e2b3..937d657dd5510fa7e6d533e1c38945a4bca80cc7 100644
--- a/src/lib/patterns/CheckerboardPattern.stories.svelte
+++ b/src/lib/patterns/CheckerboardPattern.stories.svelte
@@ -8,10 +8,14 @@
 		// tags: ['autodocs'],
 		argTypes: {
 			scale: { name: 'Scale', control: 'number' },
-			scrollDirection: { name: 'Scroll Direction', control: 'radio', options: [null, 'up-left'] },
+			scrollDirection: {
+				name: 'Scroll Direction',
+				control: 'radio',
+				options: [null, 'up-left']
+			}
 		},
 		args: {
-			scale: 1,
+			scale: 1
 		}
 	});
 </script>
@@ -36,20 +40,14 @@
 	{/snippet}
 </Story>
 
-<Story
-	name="Scaled Pattern"
-	args={{ scale: 4 }}
->
+<Story name="Scaled Pattern" args={{ scale: 4 }}>
 	{#snippet children(args)}
 		<BarbiTheme />
 		<CheckerboardPattern {...args} />
 	{/snippet}
 </Story>
 
-<Story
-	name="Scrolling Pattern"
-	args={{ scale: 4, scrollDirection: 'up-left' }}
->
+<Story name="Scrolling Pattern" args={{ scale: 4, scrollDirection: 'up-left' }}>
 	{#snippet children(args)}
 		<BarbiTheme />
 		<CheckerboardPattern {...args} />
diff --git a/src/lib/patterns/CheckerboardPattern.svelte b/src/lib/patterns/CheckerboardPattern.svelte
index 9269a47a64e2d810211e3518c21c0f507732e7cc..6fb0bfdae6c170f515d6752a1fc077afd446324a 100644
--- a/src/lib/patterns/CheckerboardPattern.svelte
+++ b/src/lib/patterns/CheckerboardPattern.svelte
@@ -1,13 +1,18 @@
 <script>
-	const { scale = 1, scrollDirection = undefined, class: className = undefined, ...props } = $props();
+	const {
+		scale = 1,
+		scrollDirection = undefined,
+		class: className = undefined,
+		...props
+	} = $props();
 
-	let height = $state(0)
-	let width = $state(0)
+	let height = $state(0);
+	let width = $state(0);
 </script>
 
 <svg
 	xmlns="http://www.w3.org/2000/svg"
-	viewBox="0 0 {width/scale} {height/scale}"
+	viewBox="0 0 {width / scale} {height / scale}"
 	width="100%"
 	height="100%"
 	class={className}
@@ -16,7 +21,13 @@
 	{...props}
 >
 	<defs>
-		<pattern id="checks" patternUnits="userSpaceOnUse" width="16" height="16" class={scrollDirection ? ['scroll', scrollDirection] : []}>
+		<pattern
+			id="checks"
+			patternUnits="userSpaceOnUse"
+			width="16"
+			height="16"
+			class={scrollDirection ? ['scroll', scrollDirection] : []}
+		>
 			<rect width="16" height="16" fill="var(--pattern-light)" />
 			<rect width="8" height="8" x="0" y="0" fill="var(--pattern-dark)" />
 			<rect width="8" height="8" x="8" y="8" fill="var(--pattern-dark)" />
@@ -26,20 +37,20 @@
 </svg>
 
 <style>
-		.scroll {
-				animation: scroll-up-left 2s linear infinite;
-		}
+	.scroll {
+		animation: scroll-up-left 2s linear infinite;
+	}
 
-		.scroll.up-left {
-				animation-name: scroll-up-left;
-		}
+	.scroll.up-left {
+		animation-name: scroll-up-left;
+	}
 
-		@keyframes scroll-up-left {
-				0% {
-						transform: translate3d(0px, 0px, 0px);
-				}
-				100% {
-						transform: translate3d(-16px, -16px, 0px);
-				}
-    }
-</style>
\ No newline at end of file
+	@keyframes scroll-up-left {
+		0% {
+			transform: translate3d(0px, 0px, 0px);
+		}
+		100% {
+			transform: translate3d(-16px, -16px, 0px);
+		}
+	}
+</style>
diff --git a/src/lib/patterns/icons/AngleDownIcon.svelte b/src/lib/patterns/icons/AngleDownIcon.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..acd641496db2b7afab6c8335bd78b42d267a0741
--- /dev/null
+++ b/src/lib/patterns/icons/AngleDownIcon.svelte
@@ -0,0 +1,15 @@
+<script>
+	const { class: className, ...props } = $props();
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	class={['icon', className]}
+	viewBox="0 0 448 512"
+	{...props}
+>
+	<path
+		style="fill: var(--colour-icon)"
+		d="M201.4 374.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 306.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z"
+	/>
+</svg>
diff --git a/src/lib/theme/BarbiTheme.svelte b/src/lib/theme/BarbiTheme.svelte
index 110b78aeb160130e61141ce7b4986b87090a44d4..88e330e54e7793e80bfa58fa3632ad5d84fb8a18 100644
--- a/src/lib/theme/BarbiTheme.svelte
+++ b/src/lib/theme/BarbiTheme.svelte
@@ -64,10 +64,10 @@
 			}
 
 			button {
-          font-family: var(--font-family-body);
-          font-size: var(--font-size-base);
-          color: var(--colour-text);
-          line-height: 1.25;
+				font-family: var(--font-family-body);
+				font-size: var(--font-size-base);
+				color: var(--colour-text);
+				line-height: 1.25;
 			}
 
 			/* Set core body defaults */
@@ -108,6 +108,8 @@
 
 					--colour-text: var(--colour-contrast);
 					--colour-shadow: var(--colour-contrast);
+					--colour-icon: var(--colour-text);
+					--colour-icon-alt: var(--colour-brick);
 
 					--pattern-light: var(--colour-background);
 					--pattern-dark: var(--colour-steel);
@@ -134,6 +136,8 @@
 					--spacing-lg: 2rem;
 					--spacing-xl: 4rem;
 
+					--size-icon: var(--spacing-lg);
+
 					/* Shadows */
 					--shadow-sm: 3px 3px 0 var(--colour-shadow);
 					--shadow-md: 5px 5px 0 var(--colour-shadow);
@@ -160,6 +164,13 @@
 						hsl(360deg 100% 60%)
 					);
 				}
+
+				/* Shared Component Styles */
+				.icon {
+				  width: var(--size-icon);
+				  height: var(--size-icon);
+				  aspect-ratio: 1;
+				}
 		</style>
 	`}
 </svelte:head>
diff --git a/src/lib/utilTypes.ts b/src/lib/utilTypes.ts
new file mode 100644
index 0000000000000000000000000000000000000000..91afa08332caa17527a42212166f4219489691bb
--- /dev/null
+++ b/src/lib/utilTypes.ts
@@ -0,0 +1 @@
+export type BarbiClass = string | string[] | Record<string, boolean> | BarbiClass[] | null | false;
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index ce1ad1307b4ce20bceb3ccd52809fafed1b7a7e6..fb0d6e86a2f6cf2c290b0415cacf6614e2212d80 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,21 +1,35 @@
 <script>
-	import BarbiTheme from '$lib/theme/BarbiTheme.svelte';
-	import Panel from '$lib/atoms/Panel.svelte';
+	import { Button, Popover, Anchor, Panel, BarbiTheme } from '$lib';
+	import AngleDownIcon from '$lib/patterns/icons/AngleDownIcon.svelte';
+	import Dropdown from '$lib/components/modal/Dropdown.svelte';
 </script>
 
 <BarbiTheme />
-
-<div style="background-color: var(--colour-primary); width: 100vw; height: 100vh">
-	<Panel tag="a" space="sm" class="panel-navbar" hover>Cool stuff</Panel>
+<div class="sss">
+	<Dropdown
+		label="This is a dropdown"
+		options={[
+			{
+				label: 'First Option',
+				value: '123',
+				onClick() {
+					console.log("FOO")
+				}
+			},
+			{ label: 'Second Option', value: '456', href: 'https://example.com' }
+		]}
+	/>
 </div>
 
-<h1>Welcome to your library project</h1>
-<p>Create your package using @sveltejs/package and preview/showcase your work with SvelteKit</p>
-<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
-
-<div>foo</div>
-
 <style>
+	.sss {
+		width: 100vw;
+		height: 100vh;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+	}
 	:global {
 		.panel-navbar {
 			/*width: 100%;*/