diff --git a/src/index.html b/src/index.html
index 16866d35eff18575d1615c5a7640f923e7a52ba1..975b3139977ce57cd90ba0953763e1bbec3941bd 100644
--- a/src/index.html
+++ b/src/index.html
@@ -307,8 +307,8 @@
 <div class="container control">
   <div class="controls">
     <div class="io">
-      <input id="data" />
-      <button id="copy">Copy</button>
+      <button id="save">Save Theme</button><button><label for="load">Load Theme</label></button>
+      <input id="load" type="file" accept=".json,application/json"/>
     </div>
     <div class="io">
         <button id="loadMaterialLight">Load Material Light</button>
diff --git a/src/script.js b/src/script.js
index 080e0a4b018baf407f83dab580cae6ece44f1ea5..4d4696f7d916ff18462e0347278590269f29b502 100644
--- a/src/script.js
+++ b/src/script.js
@@ -55,9 +55,6 @@ function updateColor(id, skipSave) {
   const color = inputColor.value;
   const opacity = inputOpacity.value / 255.0;
   cssRoot.style.setProperty("--"+id, hexToRgba(color, opacity));
-  if (skipSave !== true) {
-    data.value = JSON.stringify(save());
-  }
 }
 
 function normalizeColor(value) {
@@ -115,37 +112,34 @@ function save() {
   return json;
 }
 
+function download(filename, content) {
+    const element = document.createElement("a");
+    const data = new Blob([content], {type : 'application/json'});
+    element.setAttribute("href", URL.createObjectURL(data));
+    element.setAttribute("download", filename);
+    element.style.display = "none";
+    document.body.appendChild(element);
+    element.click();
+    document.body.removeChild(element);
+}
+
 const data = document.getElementById("data");
-const buttonCopy = document.getElementById("copy");
+const buttonSave = document.getElementById("save");
+const inputLoad = document.getElementById("load");
 
-data.addEventListener("keyup", () => {
-  try {
-      load(JSON.parse(data.value));
-  } catch (_) {
-      // ignore
-  }
+buttonSave.addEventListener("click", () => {
+  download("quasseldroid-theme.json", JSON.stringify(save()));
 });
-
-data.addEventListener("blur", () => {
-  try {
-      load(JSON.parse(data.value));
-      data.value = JSON.stringify(save());
-  } catch (_) {
-      // ignore
+inputLoad.addEventListener("change", () => {
+  if (inputLoad.files) {
+    inputLoad.files[0].text().then(raw => load(JSON.parse(raw)));
   }
 });
 
-buttonCopy.addEventListener("click", () => {
-  data.value = JSON.stringify(save());
-  data.select();
-  document.execCommand("copy");
-});
-
 themes.forEach((theme) => {
   const button = document.getElementById("load"+theme);
   button.addEventListener("click", () => {
     load(themeData[theme]);
-    data.value = JSON.stringify(save());
   });
 });
 
@@ -156,9 +150,6 @@ function updateOption(option, skipSave) {
   } else {
     delete cssRoot.dataset[option];
   }
-  if (skipSave !== true) {
-    data.value = JSON.stringify(save());
-  }
 }
 
 options.forEach((option) => {
@@ -168,4 +159,3 @@ options.forEach((option) => {
   });
   updateOption(option, true);
 });
-data.value = JSON.stringify(save());
diff --git a/src/style.css b/src/style.css
index f352ddcc4396ccecd21e7c1b1e00ab04ff2bad63..30546a2f11588a857191394346fe4cc4decdd83e 100644
--- a/src/style.css
+++ b/src/style.css
@@ -11,6 +11,10 @@ body {
   align-items: center;
 }
 
+input#load {
+    display: none;
+}
+
 .column {
   display: flex;
   flex-direction: column;