diff --git a/src/App.svelte b/src/App.svelte
index 65306c99cba5bc75c13f95e6a502009e979e3922..b7c72662ce46dac6f943c3710a6f904e0353ebf9 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -8,6 +8,7 @@
 <div class="flex flex-row space-x-4 m-1.5">
   <a href="#/" class="">Home</a>
   <a href="#/upload">Upload</a>
+  <a href="#/tag">Tag</a>
   <span>:|:</span>
   <UserAuth />
 </div>
diff --git a/src/routes.js b/src/routes.js
index 73313cc54ad290f9848ccc29dd5a9282f45cddc8..65ea06640d81b1da5831432ebcb83f7962892835 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -1,9 +1,11 @@
 import Home from '@/routes/Home.svelte';
 import Upload from '@/routes/Upload.svelte';
 import ImagePost from '@/routes/ImagePost.svelte';
+import Tag from '@/routes/Tag.svelte';
 
 export default {
     '/': Home,
     '/upload': Upload,
     '/image/:snowflake': ImagePost,
+    '/tag': Tag,
 }
\ No newline at end of file
diff --git a/src/routes/Tag.svelte b/src/routes/Tag.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f3f64ee8e3baaaa2b295af57237b6e3669b4761b
--- /dev/null
+++ b/src/routes/Tag.svelte
@@ -0,0 +1,66 @@
+<script>
+    import { fetchAPI } from "@/static/js/helper";
+    import { paths } from "@/static/js/paths";
+    import { user } from "@/stores";
+
+    const tagTypes = [
+        "artist",
+        "character",
+        "copyright",
+        "generic",
+        "group",
+        "meta",
+    ];
+    let tag = "";
+    let tagType = tagTypes[0];
+    $: validTag = checkTag(tag);
+
+    function putTag() {
+        const options = {
+            method: "PUT",
+            headers: {
+                secret: $user.secret,
+            },
+        };
+        return fetchAPI(paths.TagField(tag), options);
+    }
+    function patchTagType() {
+        const options = {
+            method: "PATCH",
+            headers: {
+                "Content-Type": "application/json",
+                secret: $user.secret,
+            },
+            body: JSON.stringify({ type: tagType }),
+        };
+        return fetchAPI(paths.TagInfo(tag), options);
+    }
+    function addTag() {
+        putTag()
+            .then(() => patchTagType())
+            .finally(() => {
+                tag = "";
+                tagType = tagTypes[0];
+            });
+    }
+    function checkTag(tag) {
+        const regex = /^[a-z0-9()_-]*$/g;
+        return !tag.match(regex) || tag.length > 128;
+    }
+</script>
+
+<div class="space-x-4 m-2">
+    <input type="text" placeholder="Tag name" bind:value={tag} />
+    <select bind:value={tagType}>
+        <option disabled>Tag type</option>
+        {#each tagTypes as type}
+            <option value={type}>{type}</option>
+        {/each}
+    </select>
+    <button on:click={addTag} disabled={validTag} class="disabled:opacity-50"
+        >Add</button
+    >
+</div>
+{#if validTag}
+    <div>No space or capital letter in tag name allowed.</div>
+{/if}
diff --git a/src/static/js/paths.js b/src/static/js/paths.js
index 0eb243eb1f73468cff242c1a73d0344609fcca6b..c2021070e837248952bdc971e187aa5bfa95b572 100644
--- a/src/static/js/paths.js
+++ b/src/static/js/paths.js
@@ -12,7 +12,7 @@ let Base = "/api",
     ImageTagField = (flake, tag) => { return `${ImageTag(flake)}/${tag}` },
     Tag = Base + "/tag",
     TagField = (tag) => { return `${Tag}/${tag}` },
-    TagInfo = TagField + "/info",
+    TagInfo = (tag) => `${TagField(tag)}/info`,
     TagPage = TagField + "/page",
     TagPageField = (entry) => { return `${TagPage}/${entry}` },
     TagPageImage = TagPageField + "/image",
diff --git a/tailwind.config.cjs b/tailwind.config.cjs
index 1d618ab7f0bc6e31b1ec834393ea1c5bf937fd3f..666a77db4463c854207e6dd5824743211d40c96c 100644
--- a/tailwind.config.cjs
+++ b/tailwind.config.cjs
@@ -1,5 +1,5 @@
 module.exports = {
-  // mode: 'jit',
+  mode: 'jit',
   purge: ['./index.html', './src/**/*.{svelte,js,ts}'],
   darkMode: false, // or 'media' or 'class',
   theme: {