diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 668afe7fd..d46d0674a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -160,6 +160,15 @@ module ApplicationHelper
     output.compact_blank.join(' ')
   end
 
+  def theme_style_tags(theme)
+    if theme == 'system'
+      concat stylesheet_pack_tag('mastodon-light', media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous')
+      concat stylesheet_pack_tag('default', media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous')
+    else
+      stylesheet_pack_tag theme, media: 'all', crossorigin: 'anonymous'
+    end
+  end
+
   def cdn_host
     Rails.configuration.action_controller.asset_host
   end
diff --git a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
index 7b917ac43..9d6ff5226 100644
--- a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
+++ b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
@@ -22,23 +22,23 @@ describe('emoji', () => {
 
     it('does unicode', () => {
       expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).toEqual(
-        '
');
+        '
');
       expect(emojify('๐จโ๐ฉโ๐งโ๐ง')).toEqual(
-        '
');
-      expect(emojify('๐ฉโ๐ฉโ๐ฆ')).toEqual('
');
+        '
');
+      expect(emojify('๐ฉโ๐ฉโ๐ฆ')).toEqual('
');
       expect(emojify('\u2757')).toEqual(
-        '
');
+        '
');
     });
 
     it('does multiple unicode', () => {
       expect(emojify('\u2757 #\uFE0F\u20E3')).toEqual(
-        '
 
');
+        '
 
');
       expect(emojify('\u2757#\uFE0F\u20E3')).toEqual(
-        '
');
+        '
');
       expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).toEqual(
-        '
 
 
');
+        '
 
 
');
       expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).toEqual(
-        'foo 
 
 bar');
+        'foo 
 
 bar');
     });
 
     it('ignores unicode inside of tags', () => {
@@ -46,16 +46,16 @@ describe('emoji', () => {
     });
 
     it('does multiple emoji properly (issue 5188)', () => {
-      expect(emojify('๐๐๐')).toEqual('

');
-      expect(emojify('๐ ๐ ๐')).toEqual('
 
 
');
+      expect(emojify('๐๐๐')).toEqual('

');
+      expect(emojify('๐ ๐ ๐')).toEqual('
 
 
');
     });
 
     it('does an emoji that has no shortcode', () => {
-      expect(emojify('๐โ๐จ')).toEqual('
');
+      expect(emojify('๐โ๐จ')).toEqual('
');
     });
 
     it('does an emoji whose filename is irregular', () => {
-      expect(emojify('โ๏ธ')).toEqual('
');
+      expect(emojify('โ๏ธ')).toEqual('
');
     });
 
     it('avoid emojifying on invisible text', () => {
@@ -67,11 +67,11 @@ describe('emoji', () => {
 
     it('avoid emojifying on invisible text with nested tags', () => {
       expect(emojify('๐bar๐ด๐'))
-        .toEqual('๐bar๐ด
');
+        .toEqual('๐bar๐ด
');
       expect(emojify('๐๐๐ด๐'))
-        .toEqual('๐๐๐ด
');
+        .toEqual('๐๐๐ด
');
       expect(emojify('๐
๐ด๐'))
-        .toEqual('๐
๐ด
');
+        .toEqual('๐
๐ด
');
     });
 
     it('does not emojify emojis with textual presentation VS15 character', () => {
@@ -79,19 +79,19 @@ describe('emoji', () => {
         .toEqual('โด๏ธ');
     });
 
-    it('does an simple emoji properly', () => {
+    it('does a simple emoji properly', () => {
       expect(emojify('โโ'))
-        .toEqual('
');
+        .toEqual('
');
     });
 
     it('does an emoji containing ZWJ properly', () => {
       expect(emojify('๐โโ๏ธ๐โโ๏ธ'))
-        .toEqual('
');
+        .toEqual('
');
     });
 
     it('keeps ordering as expected (issue fixed by PR 20677)', () => {
       expect(emojify('
๐ #foo test: foo.
'))
-        .toEqual('
 #foo test: foo.
');
+        .toEqual('
 #foo test: foo.
');
     });
   });
 });
diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js
index 5918a65ed..e4aad302f 100644
--- a/app/javascript/mastodon/features/emoji/emoji.js
+++ b/app/javascript/mastodon/features/emoji/emoji.js
@@ -17,8 +17,13 @@ const emojiFilenames = (emojis) => {
 const darkEmoji = emojiFilenames(['๐ฑ', '๐', 'โซ', '๐ค', 'โฌ', 'โผ๏ธ', 'โพ', 'โผ๏ธ', 'โ๏ธ', 'โช๏ธ', '๐ฃ', '๐ณ', '๐ท', '๐ธ', 'โฃ๏ธ', '๐ถ๏ธ', 'โด๏ธ', '๐', '๐โโ๏ธ', '๐ฝ๏ธ', '๐ณ', '๐ฆ', '๐', '๐ช', '๐ณ๏ธ', '๐น๏ธ', '๐', '๐๏ธ', '๐๏ธ', '๐โโ๏ธ', '๐ค', '๐', '๐ฅ', '๐ผ', 'โ ๏ธ', '๐ฉ', '๐ฆ', '๐ผ', '๐น', '๐ฎ', '๐', '๐ด', '๐', '๐บ', '๐ฑ', '๐ฒ', '๐ฒ', '๐ชฎ', '๐ฆโโฌ']);
 const lightEmoji = emojiFilenames(['๐ฝ', 'โพ', '๐', 'โ๏ธ', '๐จ', '๐๏ธ', '๐', '๐ฅ', '๐ป', '๐', 'โ', 'โ', 'โธ๏ธ', '๐ฉ๏ธ', '๐', '๐', '๐', '๐ง๏ธ', '๐', '๐', '๐', '๐', '๐', '๐', 'โ ๏ธ', '๐จ๏ธ', '๐', '๐', '๐ฌ', '๐ญ', '๐', '๐ณ๏ธ', 'โช', 'โฌ', 'โฝ', 'โป๏ธ', 'โซ๏ธ', '๐ชฝ', '๐ชฟ']);
 
-const emojiFilename = (filename) => {
-  const borderedEmoji = (document.body && document.body.classList.contains('theme-mastodon-light')) ? lightEmoji : darkEmoji;
+/**
+ * @param {string} filename
+ * @param {"light" | "dark" } colorScheme
+ * @returns {string}
+ */
+const emojiFilename = (filename, colorScheme) => {
+  const borderedEmoji = colorScheme === "light" ? lightEmoji : darkEmoji;
   return borderedEmoji.includes(filename) ? (filename + '_border') : filename;
 };
 
@@ -92,12 +97,30 @@ const emojifyTextNode = (node, customEmojis) => {
       const { filename, shortCode } = unicodeMapping[unicode_emoji];
       const title = shortCode ? `:${shortCode}:` : '';
 
-      replacement = document.createElement('img');
-      replacement.setAttribute('draggable', 'false');
-      replacement.setAttribute('class', 'emojione');
-      replacement.setAttribute('alt', unicode_emoji);
-      replacement.setAttribute('title', title);
-      replacement.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename)}.svg`);
+      replacement = document.createElement('picture');
+
+      const isSystemTheme = !!document.body?.classList.contains('theme-system');
+
+      if(isSystemTheme) {
+        let source = document.createElement('source');
+        source.setAttribute('media', '(prefers-color-scheme: dark)');
+        source.setAttribute('srcset', `${assetHost}/emoji/${emojiFilename(filename, "dark")}.svg`);
+        replacement.appendChild(source);
+      }
+
+      let img = document.createElement('img');
+      img.setAttribute('draggable', 'false');
+      img.setAttribute('class', 'emojione');
+      img.setAttribute('alt', unicode_emoji);
+      img.setAttribute('title', title);
+
+      let theme = "light";
+
+      if(!isSystemTheme && !document.body?.classList.contains('theme-mastodon-light'))
+        theme = "dark";
+
+      img.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename, theme)}.svg`);
+      replacement.appendChild(img);
     }
 
     // Add the processed-up-to-now string and the emoji replacement
diff --git a/app/lib/themes.rb b/app/lib/themes.rb
index 243ffb9ab..4010d8443 100644
--- a/app/lib/themes.rb
+++ b/app/lib/themes.rb
@@ -11,6 +11,6 @@ class Themes
   end
 
   def names
-    @conf.keys
+    ['system'] + @conf.keys
   end
 end
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 449657f8c..0cd7fc9f4 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -27,7 +27,7 @@
     %title= html_title
 
     = stylesheet_pack_tag 'common', media: 'all', crossorigin: 'anonymous'
-    = stylesheet_pack_tag current_theme, media: 'all', crossorigin: 'anonymous'
+    = theme_style_tags current_theme
     -# Needed for the wicg-inert polyfill. It needs to be on it's own