diff --git a/index.html b/index.html
index 5dd45a1e2ab4043360a595247174176eb8fad86e..3eb390e8d10e8efbcf38fe1b8904e6a835eeca7d 100644
--- a/index.html
+++ b/index.html
@@ -8,6 +8,6 @@
   </head>
   <body>
     <div id="app"></div>
-    <script type="module" src="/src/main.tsx"></script>
+    <script type="module" src="/src/entrypoints/main.tsx"></script>
   </body>
 </html>
diff --git a/package.json b/package.json
index d64671a405dc62edfeb34df90378a916b43dda3e..881c625a834b8674e4d067801ed5ece5669df3e2 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
     "@preact/preset-vite": "^2.0.0",
     "@styled-icons/bootstrap": "^10.34.0",
     "@styled-icons/feather": "^10.34.0",
+    "@types/node": "^15.12.3",
     "@types/preact-i18n": "^2.3.0",
     "@types/styled-components": "^5.1.10",
     "preact-i18n": "^2.4.0-preactx",
diff --git a/src/app.tsx b/src/app.tsx
index c16cf4ca3884913d50fbce2b5ee499ef52ac584f..9afc23d2e4e632913373f1ffce216add288dcf50 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -1,79 +1,7 @@
-import styled, { createGlobalStyle } from 'styled-components';
-import { useState } from 'preact/hooks';
-
-import Button from './components/ui/Button';
-import Banner from './components/ui/Banner';
-import Checkbox from './components/ui/Checkbox';
-import ComboBox from './components/ui/ComboBox';
-import InputBox from './components/ui/InputBox';
-import ColourSwatches from './components/ui/ColourSwatches';
-import Tip from './components/ui/Tip';
-import Radio from './components/ui/Radio';
-import Overline from './components/ui/Overline';
-
-import Locale from './context/Locale';
-
-// ! TEMP START
-let a = {"light":false,"accent":"#FD6671","background":"#191919","foreground":"#F6F6F6","block":"#2D2D2D","message-box":"#363636","mention":"rgba(251, 255, 0, 0.06)","success":"#65E572","warning":"#FAA352","error":"#F06464","hover":"rgba(0, 0, 0, 0.1)","sidebar-active":"#FD6671","scrollbar-thumb":"#CA525A","scrollbar-track":"transparent","primary-background":"#242424","primary-header":"#363636","secondary-background":"#1E1E1E","secondary-foreground":"#C8C8C8","secondary-header":"#2D2D2D","tertiary-background":"#4D4D4D","tertiary-foreground":"#848484","status-online":"#3ABF7E","status-away":"#F39F00","status-busy":"#F84848","status-streaming":"#977EFF","status-invisible":"#A5A5A5"};
-
-const GlobalTheme = createGlobalStyle`
-:root {
-	${
-		Object.keys(a)
-			.map(key => {
-				return `--${key}: ${(a as any)[key]};`;
-			})
-	}
-}
-`;
-// ! TEMP END
-
-export const UIDemo = styled.div`
-	gap: 12px;
-	padding: 12px;
-	display: flex;
-	flex-direction: column;
-	align-items: flex-start;
-`;
-
 export function App() {
-	let [checked, setChecked] = useState(false);
-	let [colour, setColour] = useState('#FD6671');
-	let [selected, setSelected] = useState<'a' | 'b' | 'c'>('a');
-
 	return (
 		<>
-			<GlobalTheme />
-			<Locale>
-				<UIDemo>
-					<Button>Button (normal)</Button>
-					<Button contrast>Button (contrast)</Button>
-					<Button error>Button (error)</Button>
-					<Button contrast error>Button (contrast + error)</Button>
-					<Banner>I am a banner!</Banner>
-					<Checkbox checked={checked} onChange={setChecked} description="ok gamer">Do you want thing??</Checkbox>
-					<ComboBox>
-						<option>Select an option.</option>
-						<option>1</option>
-						<option>2</option>
-						<option>3</option>
-					</ComboBox>
-					<InputBox placeholder="Normal input box..." />
-					<InputBox placeholder="Contrast input box..." contrast />
-					<InputBox value="Input box with value" />
-					<InputBox value="Contrast with value" contrast />
-					<ColourSwatches value={colour} onChange={v => setColour(v)} />
-					<Tip>I am a tip! I provide valuable information.</Tip>
-					<Radio checked={selected === 'a'} onSelect={() => setSelected('a')}>First option</Radio>
-					<Radio checked={selected === 'b'} onSelect={() => setSelected('b')}>Second option</Radio>
-					<Radio checked={selected === 'c'} onSelect={() => setSelected('c')}>Last option</Radio>
-					<Overline>Normal overline</Overline>
-					<Overline type="subtle">Subtle overline</Overline>
-					<Overline type="error">Error overline</Overline>
-					<Overline error="with error">Normal overline</Overline>
-					<Overline type="subtle" error="with error">Subtle overline</Overline>
-				</UIDemo>
-			</Locale>
+			<h1>REVOLT</h1>
 		</>
 	)
 }
diff --git a/src/context/Theme.tsx b/src/context/Theme.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..580e204b537e170c27e518eec3af651139f88334
--- /dev/null
+++ b/src/context/Theme.tsx
@@ -0,0 +1,16 @@
+import { createGlobalStyle } from "styled-components";
+
+// ! TEMP START
+let a = {"light":false,"accent":"#FD6671","background":"#191919","foreground":"#F6F6F6","block":"#2D2D2D","message-box":"#363636","mention":"rgba(251, 255, 0, 0.06)","success":"#65E572","warning":"#FAA352","error":"#F06464","hover":"rgba(0, 0, 0, 0.1)","sidebar-active":"#FD6671","scrollbar-thumb":"#CA525A","scrollbar-track":"transparent","primary-background":"#242424","primary-header":"#363636","secondary-background":"#1E1E1E","secondary-foreground":"#C8C8C8","secondary-header":"#2D2D2D","tertiary-background":"#4D4D4D","tertiary-foreground":"#848484","status-online":"#3ABF7E","status-away":"#F39F00","status-busy":"#F84848","status-streaming":"#977EFF","status-invisible":"#A5A5A5"};
+
+export const GlobalTheme = createGlobalStyle`
+:root {
+	${
+		Object.keys(a)
+			.map(key => {
+				return `--${key}: ${(a as any)[key]};`;
+			})
+	}
+}
+`;
+// ! TEMP END
diff --git a/src/main.tsx b/src/main.tsx
index e847ff2237685c5825eefc46afbf5cf538aa06a9..9d425db794f3e1fbbc36ca56b39dd2512b11892d 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,5 +1,5 @@
 import { render } from 'preact'
-import './styles/index.scss'
+import '../styles/index.scss'
 import { App } from './app'
 
 render(<App />, document.getElementById('app')!)
diff --git a/src/styles/_elements.scss b/src/styles/_elements.scss
new file mode 100644
index 0000000000000000000000000000000000000000..d0e652411d01e81e1cb099267e89a5afdf677dbd
--- /dev/null
+++ b/src/styles/_elements.scss
@@ -0,0 +1,41 @@
+::-webkit-scrollbar {
+    width: 3px;
+    height: 3px;
+}
+
+::-webkit-scrollbar-track {
+    background: var(--scrollbar-track);
+}
+
+::-webkit-scrollbar-thumb {
+    background: var(--scrollbar-thumb);
+}
+
+::selection {
+    background: var(--accent);
+    color: var(--foreground);
+}
+
+::-moz-selection {
+    background: var(--accent);
+    color: var(--foreground);
+}
+
+::-webkit-selection {
+    background: var(--accent);
+    color: var(--foreground);
+}
+
+a,
+a:link,
+a:visited,
+a:hover {
+    text-decoration: none;
+    color: var(--accent);
+}
+
+hr {
+    border: 0;
+    height: 1px;
+    flex-grow: 1;
+}
diff --git a/src/styles/_fonts.scss b/src/styles/_fonts.scss
new file mode 100644
index 0000000000000000000000000000000000000000..c290de23a42def455953f372a0b8574094188029
--- /dev/null
+++ b/src/styles/_fonts.scss
@@ -0,0 +1,9 @@
+@import "@fontsource/open-sans/300.css";
+@import "@fontsource/open-sans/400.css";
+@import "@fontsource/open-sans/600.css";
+@import "@fontsource/open-sans/700.css";
+
+@import "@fontsource/open-sans/300-italic.css";
+@import "@fontsource/open-sans/400-italic.css";
+@import "@fontsource/open-sans/600-italic.css";
+@import "@fontsource/open-sans/700-italic.css";
diff --git a/src/styles/_page.scss b/src/styles/_page.scss
new file mode 100644
index 0000000000000000000000000000000000000000..210e54c3e560cb36772ff5737b768ca53cf40133
--- /dev/null
+++ b/src/styles/_page.scss
@@ -0,0 +1,31 @@
+* {
+	text-rendering: optimizeLegibility !important;
+    -webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
+
+    scrollbar-width: thin;
+    box-sizing: border-box;
+}
+
+html {
+    contain: content;
+    background: var(--background);
+    background-size: cover !important;
+    background-repeat: no-repeat !important;
+}
+
+html,
+body {
+    font-family: "Open Sans", sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    caret-color: var(--accent);
+    color: var(--foreground);
+
+    -webkit-tap-highlight-color: transparent;
+    -webkit-overflow-scrolling: touch;
+    -webkit-text-size-adjust: 100%;
+    overscroll-behavior: contain;
+
+    scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
+}
diff --git a/src/styles/index.scss b/src/styles/index.scss
index b6591dc39a391f855b41c49d7053b787994f9d84..d07896c1c562b7cac9570ba8b3e77ee6e703a3c5 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -1,86 +1,3 @@
-@import "@fontsource/open-sans/300.css";
-@import "@fontsource/open-sans/400.css";
-@import "@fontsource/open-sans/600.css";
-@import "@fontsource/open-sans/700.css";
-
-@import "@fontsource/open-sans/300-italic.css";
-@import "@fontsource/open-sans/400-italic.css";
-@import "@fontsource/open-sans/600-italic.css";
-@import "@fontsource/open-sans/700-italic.css";
-
-@import "./temp-theme.scss";
-
-* {
-	text-rendering: optimizeLegibility !important;
-    -webkit-font-smoothing: antialiased;
-	-moz-osx-font-smoothing: grayscale;
-
-    
-    scrollbar-width: thin;
-    box-sizing: border-box;
-}
-
-html {
-    contain: content;
-    background: var(--background);
-    background-size: cover !important;
-    background-repeat: no-repeat !important;
-}
-
-html,
-body {
-    font-family: "Open Sans", sans-serif;
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-    caret-color: var(--accent);
-    color: var(--foreground);
-
-    -webkit-tap-highlight-color: transparent;
-    -webkit-overflow-scrolling: touch;
-    -webkit-text-size-adjust: 100%;
-    overscroll-behavior: contain;
-
-    scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
-}
-
-::-webkit-scrollbar {
-    width: 3px;
-    height: 3px;
-}
-
-::-webkit-scrollbar-track {
-    background: var(--scrollbar-track);
-}
-
-::-webkit-scrollbar-thumb {
-    background: var(--scrollbar-thumb);
-}
-
-::selection {
-    background: var(--accent);
-    color: var(--foreground);
-}
-
-::-moz-selection {
-    background: var(--accent);
-    color: var(--foreground);
-}
-
-::-webkit-selection {
-    background: var(--accent);
-    color: var(--foreground);
-}
-
-a,
-a:link,
-a:visited,
-a:hover {
-    text-decoration: none;
-    color: var(--accent);
-}
-
-hr {
-    border: 0;
-    height: 1px;
-    flex-grow: 1;
-}
+@import "elements";
+@import "fonts";
+@import "page";
diff --git a/src/styles/temp-theme.scss b/src/styles/temp-theme.scss
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/tsconfig.json b/tsconfig.json
index d245d3629800fc6317caee465d5dd0d0cb6e1d92..56bf37aa538c4d07b77cf20e093d200013afbbb8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,5 +17,5 @@
     "jsxFactory": "h",
     "jsxFragmentFactory": "Fragment"
   },
-  "include": ["src"]
+  "include": ["src", "ui/ui.tsx"]
 }
diff --git a/ui/index.html b/ui/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..fc1e6dc908f66a1f81a4cd2c87f2ba94f7ca3f87
--- /dev/null
+++ b/ui/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>REVOLT UI</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/ui/ui.tsx"></script>
+  </body>
+</html>
diff --git a/ui/ui.tsx b/ui/ui.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0e642dad7bda5d60efa31302e3cff37639fe4e97
--- /dev/null
+++ b/ui/ui.tsx
@@ -0,0 +1,68 @@
+import { useState } from 'preact/hooks';
+import styled from 'styled-components';
+import '../src/styles/index.scss'
+import { render } from 'preact'
+
+import { GlobalTheme } from '../src/context/Theme';
+
+export const UIDemo = styled.div`
+	gap: 12px;
+	padding: 12px;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+`;
+
+import Button from '../src/components/ui/Button';
+import Banner from '../src/components/ui/Banner';
+import Checkbox from '../src/components/ui/Checkbox';
+import ComboBox from '../src/components/ui/ComboBox';
+import InputBox from '../src/components/ui/InputBox';
+import ColourSwatches from '../src/components/ui/ColourSwatches';
+import Tip from '../src/components/ui/Tip';
+import Radio from '../src/components/ui/Radio';
+import Overline from '../src/components/ui/Overline';
+
+export function UI() {
+	let [checked, setChecked] = useState(false);
+	let [colour, setColour] = useState('#FD6671');
+	let [selected, setSelected] = useState<'a' | 'b' | 'c'>('a');
+
+    return (
+        <>
+            <Button>Button (normal)</Button>
+            <Button contrast>Button (contrast)</Button>
+            <Button error>Button (error)</Button>
+            <Button contrast error>Button (contrast + error)</Button>
+            <Banner>I am a banner!</Banner>
+            <Checkbox checked={checked} onChange={setChecked} description="ok gamer">Do you want thing??</Checkbox>
+            <ComboBox>
+                <option>Select an option.</option>
+                <option>1</option>
+                <option>2</option>
+                <option>3</option>
+            </ComboBox>
+            <InputBox placeholder="Normal input box..." />
+            <InputBox placeholder="Contrast input box..." contrast />
+            <InputBox value="Input box with value" />
+            <InputBox value="Contrast with value" contrast />
+            <ColourSwatches value={colour} onChange={v => setColour(v)} />
+            <Tip>I am a tip! I provide valuable information.</Tip>
+            <Radio checked={selected === 'a'} onSelect={() => setSelected('a')}>First option</Radio>
+            <Radio checked={selected === 'b'} onSelect={() => setSelected('b')}>Second option</Radio>
+            <Radio checked={selected === 'c'} onSelect={() => setSelected('c')}>Last option</Radio>
+            <Overline>Normal overline</Overline>
+            <Overline type="subtle">Subtle overline</Overline>
+            <Overline type="error">Error overline</Overline>
+            <Overline error="with error">Normal overline</Overline>
+            <Overline type="subtle" error="with error">Subtle overline</Overline>
+        </>
+    )
+}
+
+render(<>
+    <GlobalTheme />
+    <UIDemo>
+        <UI />
+    </UIDemo>
+</>, document.getElementById('app')!)
diff --git a/vite.config.ts b/vite.config.ts
index e3bdaffe854595ab8167679af0bacf5413a75f08..603d5d4a05f70cb0b14de59100a5bc7abfc9ac83 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,7 +1,16 @@
+import { resolve } from 'path'
 import { defineConfig } from 'vite'
 import preact from '@preact/preset-vite'
 
 // https://vitejs.dev/config/
 export default defineConfig({
-  plugins: [preact()]
+  plugins: [preact()],
+  build: {
+    rollupOptions: {
+      input: {
+        main: resolve(__dirname, 'index.html'),
+        ui: resolve(__dirname, 'ui/index.html')
+      }
+    }
+  }
 })
diff --git a/yarn.lock b/yarn.lock
index f68abfd6d86cba6d7216f56fb3a366a748d46108..cc3c7a1e342a18d1221fbe9154e16d816a102d8e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -318,6 +318,11 @@
     "@types/react" "*"
     hoist-non-react-statics "^3.3.0"
 
+"@types/node@^15.12.3":
+  version "15.12.3"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.3.tgz#2817bf5f25bc82f56579018c53f7d41b1830b1af"
+  integrity sha512-SNt65CPCXvGNDZ3bvk1TQ0Qxoe3y1RKH88+wZ2Uf05dduBCqqFQ76ADP9pbT+Cpvj60SkRppMCh2Zo8tDixqjQ==
+
 "@types/preact-i18n@^2.3.0":
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/@types/preact-i18n/-/preact-i18n-2.3.0.tgz#d99d4a9ad03b0b65e57ed4d874447de74384e32f"