diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 91152ef6e675788b24dfd40a0e0ddc025759257a..46799e0597eead6f2eb398562fe17fa5ccc517c0 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -14,6 +14,8 @@ export default defineConfig({
         text: "Components",
         items: [
 			    { text: "Pill", link: "/components/pill/" },
+			    { text: "Alert", link: "/components/alert/" },
+			    { text: "Modal", link: "/components/modal/" },
 			    { text: "Loader", link: "/components/loader/" },
 			    { text: "Activity", link: "/components/activity/" },
 			    { text: "Card", items: [
diff --git a/docs/components/alert/index.md b/docs/components/alert/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..b3b8de9ced9c8db70bebe57d1a97bd8b2f0263c4
--- /dev/null
+++ b/docs/components/alert/index.md
@@ -0,0 +1,102 @@
+# Alert
+
+
+| Prop          | Data type                                          | Required? | Default     | Description             |
+| ------------- | -------------------------------------------------- | --------- | ----------- | ----------------------- |
+| `color`       | `blue` \| `red` \| `purple` \| `green` \| `yellow` | No        | `secondary` | Renders a colored alert |
+
+
+
+## Alert colors
+
+Funkwhale alert support a range of pastel colors to create visually appealing interfaces.
+
+::: details Colors
+
+- Red
+- Blue
+- Purple
+- Green
+- Yellow
+
+:::
+
+### Blue
+
+```vue-html
+<fw-alert color="blue">
+  Blue alert
+</fw-alert>
+```
+
+<fw-alert color="blue">
+  Blue alert
+</fw-alert>
+
+### Red
+
+```vue-html
+<fw-alert color="red">
+  Red alert
+</fw-alert>
+```
+
+<fw-alert color="red">
+  Red alert
+</fw-alert>
+
+### Purple
+
+```vue-html
+<fw-alert color="purple">
+  Purple alert
+</fw-alert>
+```
+
+<fw-alert color="purple">
+  Purple alert
+</fw-alert>
+
+### Green
+
+```vue-html
+<fw-alert color="green">
+  Green alert
+</fw-alert>
+```
+
+<fw-alert color="green">
+  Green alert
+</fw-alert>
+
+### Yellow
+
+```vue-html
+<fw-alert color="yellow">
+  Yellow alert
+</fw-alert>
+```
+
+<fw-alert color="yellow">
+  Yellow alert
+</fw-alert>
+
+## Alert actions
+
+```vue-html{2-4}
+<fw-alert>
+  Awesome artist
+
+  <template #actions>
+    <fw-button>Got it</fw-button>
+  </template>
+</fw-alert>
+```
+
+<fw-alert>
+  Awesome artist
+  <template #actions>
+    <fw-button>Got it</fw-button>
+  </template>
+</fw-alert>
+
diff --git a/docs/components/modal/index.md b/docs/components/modal/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..0c6935bd632b1cfec97d08a74e953e8e4674a75d
--- /dev/null
+++ b/docs/components/modal/index.md
@@ -0,0 +1,164 @@
+<script setup lang="ts">
+import { ref, watchEffect } from 'vue'
+
+const open = ref(false)
+const open2 = ref(false)
+
+const open3 = ref(false)
+const alertOpen = ref(true)
+watchEffect(() => {
+  if (open3.value === false) {
+    alertOpen.value = true
+    }
+})
+
+const open4 = ref(false)
+const open5 = ref(false)
+
+</script>
+
+# Modal
+
+
+| Prop          | Data type         | Required? | Default | Description              |
+| ------------- | ----------------- | --------- | ------- | ------------------------ |
+| `title`       | `string`          | Yes       |         | Modal title              |
+| `v-model`     | `true` \| `false` | Yes       |         | Should the modal be open |
+
+
+
+## Modal open
+
+
+```vue-html
+<fw-modal v-model="open" title="My modal">
+  Modal content
+</fw-modal>
+
+<fw-button @click="open = true">
+  Open modal
+</fw-button>
+```
+
+<fw-modal v-model="open" title="My modal">
+  Modal content
+</fw-modal>
+<fw-button @click="open = true">
+  Open modal
+</fw-button>
+
+## Modal actions
+
+```vue-html
+<fw-modal v-model="open" title="My modal">
+  Modal content
+
+  <template #actions>
+    <fw-button @click="open = false" color="secondary">
+      Cancel
+    </fw-button>
+
+    <fw-button @click="open = false">
+      Ok
+    </fw-button>
+  </template>
+</fw-modal>
+
+<fw-button @click="open = true">
+  Open modal
+</fw-button>
+```
+
+<fw-modal v-model="open2" title="My modal">
+  Modal content
+  <template #actions>
+    <fw-button @click="open2 = false" color="secondary">
+      Cancel
+    </fw-button>
+    <fw-button @click="open2 = false">
+      Ok
+    </fw-button>
+  </template>
+</fw-modal>
+<fw-button @click="open2 = true">
+  Open modal
+</fw-button>
+
+## Nested modals
+
+```vue-html
+<fw-modal v-model="open" title="My modal">
+  Modal content
+
+  <template #actions>
+    <fw-button @click="open = false" color="secondary">
+      Cancel
+    </fw-button>
+
+    <fw-button @click="open = false">
+      Ok
+    </fw-button>
+  </template>
+</fw-modal>
+
+<fw-button @click="open = true">
+  Open modal
+</fw-button>
+```
+
+<fw-modal v-model="open4" title="My modal">
+<fw-modal v-model="open5" title="My modal">
+  Nested modal content
+</fw-modal>
+  <fw-button @click="open5 = true">
+    Open nested modal
+  </fw-button>
+</fw-modal>
+<fw-button @click="open4 = true">
+  Open modal
+</fw-button>
+
+## Alert inside modal
+
+```vue-html
+<fw-modal v-model="open" title="My modal">
+  Modal content
+
+  <template #alert v-if="alertOpen">
+    <fw-alert>
+      Alert content
+
+      <template #actions>
+        <fw-button @click="alertOpen = false">Close alert</fw-button>
+      </template>
+    </fw-alert>
+  </template>
+</fw-modal>
+
+<fw-button @click="open = true">
+  Open modal
+</fw-button>
+```
+
+<fw-modal v-model="open3" title="My modal">
+  Modal content
+  <template #alert v-if="alertOpen">
+    <fw-alert>
+      Alert content
+      <template #actions>
+        <fw-button @click="alertOpen = false">Close alert</fw-button>
+      </template>
+    </fw-alert>
+  </template>
+  <template #actions>
+    <fw-button @click="open3 = false" color="secondary">
+      Cancel
+    </fw-button>
+    <fw-button @click="open3 = false">
+      Ok
+    </fw-button>
+  </template>
+</fw-modal>
+<fw-button @click="open3 = true">
+  Open modal
+</fw-button>
diff --git a/src/components/alert/Alert.vue b/src/components/alert/Alert.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c4e13e4bc135dff895e7423e03921c9f854fbb7f
--- /dev/null
+++ b/src/components/alert/Alert.vue
@@ -0,0 +1,23 @@
+<script setup lang="ts">
+import { useColorOrPastel } from '~/composables/colors'
+
+const props = defineProps<PastelProps>()
+const color = useColorOrPastel(() => props.color, 'blue')
+</script>
+
+<template>
+  <div
+    class="funkwhale is-colored alert"
+    :class="[color]"
+  >
+    <slot />
+
+    <div v-if="$slots.actions" class="actions">
+      <slot name="actions" />
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/alert/style.scss b/src/components/alert/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..7ad80ae919b4d4c7e030ebbed425c5c0c0a47cee
--- /dev/null
+++ b/src/components/alert/style.scss
@@ -0,0 +1,45 @@
+.funkwhale.alert {
+  color: var(--fw-gray-900);
+
+  @include light-theme {
+    background-color: var(--fw-pastel-1, var(--fw-bg-color));
+
+    > .actions .funkwhale.button {
+      --fw-bg-color: var(--fw-pastel-2);
+
+      &:hover, &.is-hovered {
+        --fw-bg-color: var(--fw-pastel-3);
+      }
+
+      &:active, &.is-active {
+        --fw-bg-color: var(--fw-pastel-4);
+      }
+    }
+  }
+
+  @include dark-theme {
+    background-color: var(--fw-pastel-4, var(--fw-bg-color));
+
+    > .actions .funkwhale.button {
+      --fw-bg-color: var(--fw-pastel-3);
+      box-shadow: 0 2px 4px 2px rgba(#000, 0.1);
+
+      &:hover, &.is-hovered {
+        --fw-bg-color: var(--fw-pastel-2);
+      }
+
+      &:active, &.is-active {
+        --fw-bg-color: var(--fw-pastel-1);
+      }
+    }
+  }
+
+  padding: 0.625rem 2rem;
+  line-height: 1.2;
+  display: flex;
+  align-items: center;
+
+  > .actions {
+    margin-left: auto;
+  }
+}
diff --git a/src/components/index.ts b/src/components/index.ts
index 095bfe399d44a8568d91b72a5a5311b0cd76d7f4..72fdb4fa7dbf64f3f9ef2732b67232e3e4add79f 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -43,3 +43,9 @@ export { default as FwTab } from './tabs/Tab.vue'
 // Utils
 export { default as FwSanitizedHtml } from './utils/SanitizedHtml.vue'
 export { default as FwMarkdown } from './utils/Markdown.vue'
+
+// Modal
+export { default as FwModal } from './modal/Modal.vue'
+
+// Alert
+export { default as FwAlert } from './alert/Alert.vue'
diff --git a/src/components/modal/Modal.vue b/src/components/modal/Modal.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6501e1605bb175b7f1a82f5c960c03e702358d20
--- /dev/null
+++ b/src/components/modal/Modal.vue
@@ -0,0 +1,37 @@
+<script setup lang="ts">
+const title = defineProp<string>('title', { required: true })
+const open = defineModel<boolean>({ required: true })
+</script>
+
+<template>
+  <Teleport to="body">
+    <Transition mode="out-in">
+      <div v-if="open" @click.exact.stop="open = false" class="funkwhale overlay">
+        <div @click.stop class="funkwhale modal">
+          <h2>{{ title }}</h2>
+
+          <Transition>
+            <div v-if="$slots.alert" class="alert-container">
+              <div>
+                <slot name="alert" />
+              </div>
+            </div>
+          </Transition>
+          <hr />
+
+          <div class="modal-content">
+            <slot />
+          </div>
+          <hr  v-if="$slots.actions" />
+          <div v-if="$slots.actions" class="modal-actions">
+            <slot name="actions" />
+          </div>
+        </div>
+      </div>
+    </Transition>
+  </Teleport>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/modal/style.scss b/src/components/modal/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..6a0c898e6c9948b0d82fe2d1acde18ae2703bed4
--- /dev/null
+++ b/src/components/modal/style.scss
@@ -0,0 +1,85 @@
+.funkwhale.modal {
+  background: var(--fw-bg-color);
+
+  box-shadow: 0 2px 4px 2px rgba(#000, 0.2);
+  border-radius: 1rem;
+  min-width: 32rem;
+  max-width: 90vw;
+
+  > hr {
+    padding: 0;
+    margin: 0;
+  }
+
+  > .alert-container {
+    display: grid;
+    grid-template-rows: 1fr;
+    margin-bottom: -1px;
+
+    &.v-enter-active,
+    &.v-leave-active {
+      transition: grid-template-rows 0.2s ease;
+    }
+
+    &.v-enter-from,
+    &.v-leave-to {
+      grid-template-rows: 0fr;
+    }
+
+    > div {
+      overflow: hidden;
+    }
+  }
+
+  > h2 {
+    font-size: 1.25em;
+    padding: 1.625rem 4.5rem 1rem;
+    line-height: 1.2;
+    text-align: center;
+  }
+
+  .modal-content {
+    padding: 1rem 2rem 1rem;
+  }
+
+  .modal-actions {
+    padding: 0.75rem 2rem 1rem;
+  }
+}
+
+.funkwhale.overlay {
+  background: rgba(#000, .2);
+
+  position: fixed;
+  inset: 0;
+
+  z-index: 9001;
+
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  &.v-enter-active,
+  &.v-leave-active {
+    transition: opacity 0.2s ease;
+
+    .funkwhale.modal {
+      transition: transform 0.2s ease;
+    }
+  }
+
+  &.v-enter-from,
+  &.v-leave-to {
+    opacity: 0;
+
+    .funkwhale.modal {
+      transform: translateY(1rem);
+    }
+  }
+
+  &.v-leave-to {
+    .funkwhale.modal {
+      transform: translateY(-1rem);
+    }
+  }
+}
diff --git a/src/styles/base/generic.scss b/src/styles/base/generic.scss
index 5a346354a8856837a6d1fbd91a9a8384eba2208c..ba94089e0f7fd13a8c56223feaeedc1611f8bc23 100644
--- a/src/styles/base/generic.scss
+++ b/src/styles/base/generic.scss
@@ -50,6 +50,7 @@
 
 // Horizontal rule
 %hr {
+  border: none;
   border-bottom: 1px solid var(--fw-border-color);
 
   @include light-theme {
@@ -62,7 +63,6 @@
 
   padding: 8px 0 0 0;
   margin: 0 0 8px 0;
-  border: none;
 }
 
 hr:not(.is-raw) {
diff --git a/src/styles/funkwhale.scss b/src/styles/funkwhale.scss
index 6cb4ca2ec4ae8d4ce46f03ad059edaf5c74cfded..b4260465b043f67c3b1003da75a5de1e732fc37c 100644
--- a/src/styles/funkwhale.scss
+++ b/src/styles/funkwhale.scss
@@ -3,9 +3,9 @@
 @import "font";
 @import "colors";
 
-body {
+html, body {
   font-family: $font-main;
-  font-size: 16px;
+  font-size: 16px !important;
   // background-color: var(--fw-bg-color);
 }