diff --git a/web/source/settings-panel/components/login.jsx b/web/source/settings-panel/components/login.jsx
index c4bebd063..86d9c6a12 100644
--- a/web/source/settings-panel/components/login.jsx
+++ b/web/source/settings-panel/components/login.jsx
@@ -36,7 +36,7 @@ module.exports = function Login({error}) {
 		let currentDomain = window.location.origin;
 		Promise.try(() => {
 			console.log("trying", currentDomain);
-			return dispatch(api.instance.fetch(currentDomain));
+			return dispatch(api.instance.fetchWithoutStore(currentDomain));
 		}).then(() => {
 			if (instanceFieldRef.current.length == 0) { // user hasn't started typing yet
 				dispatch(setInstance(currentDomain));
@@ -51,7 +51,7 @@ module.exports = function Login({error}) {
 	function tryInstance() {
 		let domain = instanceFieldRef.current;
 		Promise.try(() => {
-			return dispatch(api.instance.fetch(domain)).catch((e) => {
+			return dispatch(api.instance.fetchWithoutStore(domain)).catch((e) => {
 				// TODO: clearer error messages for common errors
 				console.log(e);
 				throw e;
diff --git a/web/source/settings-panel/index.js b/web/source/settings-panel/index.js
index d59dc7237..e4b050055 100644
--- a/web/source/settings-panel/index.js
+++ b/web/source/settings-panel/index.js
@@ -40,7 +40,6 @@ const nav = {
 		entries: {
 			"Profile": require("./user/profile.js"),
 			"Settings": require("./user/settings.js"),
-			"Customization": require("./user/customization.js")
 		}
 	},
 	"Admin": {
@@ -76,6 +75,9 @@ function App() {
 					return dispatch(api.oauth.tokenize(code));
 				}
 			}
+		}).then(() => {
+			// Fetch current instance info
+			return dispatch(api.instance.fetch());
 		}).then(() => {
 			// Check currently stored auth token for validity if available
 			if (loginState == "callback" || loginState == "login") {
diff --git a/web/source/settings-panel/lib/api/index.js b/web/source/settings-panel/lib/api/index.js
index 3bd361d78..6a99ed1e9 100644
--- a/web/source/settings-panel/lib/api/index.js
+++ b/web/source/settings-panel/lib/api/index.js
@@ -22,7 +22,7 @@ const Promise = require("bluebird");
 const { isPlainObject } = require("is-plain-object");
 
 const { APIError } = require("../errors");
-const { setInstanceInfo } = require("../../redux/reducers/instances").actions;
+const { setInstanceInfo, setNamedInstanceInfo } = require("../../redux/reducers/instances").actions;
 const oauth = require("../../redux/reducers/oauth").actions;
 
 function apiCall(method, route, payload, type="json") {
@@ -95,7 +95,7 @@ function getCurrentUrl() {
 	return `${window.location.origin}${window.location.pathname}`;
 }
 
-function fetchInstance(domain) {
+function fetchInstanceWithoutStore(domain) {
 	return function(dispatch, getState) {
 		return Promise.try(() => {
 			let lookup = getState().instances.info[domain];
@@ -113,7 +113,20 @@ function fetchInstance(domain) {
 			return apiCall("GET", "/api/v1/instance")(dispatch, () => fakeState);
 		}).then((json) => {
 			if (json && json.uri) { // TODO: validate instance json more?
-				dispatch(setInstanceInfo([domain, json]));
+				dispatch(setNamedInstanceInfo([domain, json]));
+				return json;
+			}
+		});
+	};
+}
+
+function fetchInstance() {
+	return function(dispatch, _getState) {
+		return Promise.try(() => {
+			return dispatch(apiCall("GET", "/api/v1/instance"));
+		}).then((json) => {
+			if (json && json.uri) {
+				dispatch(setInstanceInfo(json));
 				return json;
 			}
 		});
@@ -122,6 +135,7 @@ function fetchInstance(domain) {
 
 module.exports = {
 	instance: {
+		fetchWithoutStore: fetchInstanceWithoutStore,
 		fetch: fetchInstance
 	},
 	oauth: require("./oauth")({apiCall, getCurrentUrl}),
diff --git a/web/source/settings-panel/lib/api/user.js b/web/source/settings-panel/lib/api/user.js
index 94bbeb920..a24bccaff 100644
--- a/web/source/settings-panel/lib/api/user.js
+++ b/web/source/settings-panel/lib/api/user.js
@@ -64,7 +64,7 @@ module.exports = function ({ apiCall }) {
 			};
 		},
 		updateProfile: function updateProfile() {
-			const formKeys = ["display_name", "locked", "source"];
+			const formKeys = ["display_name", "locked", "source", "custom_css"];
 			const renamedKeys = [["note", "source.note"]];
 			const fileKeys = ["header", "avatar"];
 
diff --git a/web/source/settings-panel/redux/reducers/instances.js b/web/source/settings-panel/redux/reducers/instances.js
index 668035d6f..de874662b 100644
--- a/web/source/settings-panel/redux/reducers/instances.js
+++ b/web/source/settings-panel/redux/reducers/instances.js
@@ -26,9 +26,12 @@ module.exports = createSlice({
 		info: {},
 	},
 	reducers: {
-		setInstanceInfo: (state, {payload}) => {
+		setNamedInstanceInfo: (state, {payload}) => {
 			let [key, info] = payload;
 			state.info[key] = info;
 		},
+		setInstanceInfo: (state, {payload}) => {
+			state.current = payload;
+		}
 	}
 });
\ No newline at end of file
diff --git a/web/source/settings-panel/user/customization.js b/web/source/settings-panel/user/customization.js
deleted file mode 100644
index 8a029e305..000000000
--- a/web/source/settings-panel/user/customization.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
-	GoToSocial
-	Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
-
-	This program is free software: you can redistribute it and/or modify
-	it under the terms of the GNU Affero General Public License as published by
-	the Free Software Foundation, either version 3 of the License, or
-	(at your option) any later version.
-
-	This program is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-	GNU Affero General Public License for more details.
-
-	You should have received a copy of the GNU Affero General Public License
-	along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-"use strict";
-
-module.exports = function UserCustomization() {
-	return "user customization";
-};
\ No newline at end of file
diff --git a/web/source/settings-panel/user/profile.js b/web/source/settings-panel/user/profile.js
index 25e22c4cf..f06f0e667 100644
--- a/web/source/settings-panel/user/profile.js
+++ b/web/source/settings-panel/user/profile.js
@@ -31,6 +31,9 @@ const user = require("../redux/reducers/user").actions;
 module.exports = function UserProfile() {
 	const dispatch = Redux.useDispatch();
 	const account = Redux.useSelector(state => state.user.profile);
+	const instance = Redux.useSelector(state => state.instances.current);
+
+	const allowCustomCSS = instance.configuration.accounts.allow_custom_css;
 
 	const { onTextChange, onCheckChange, onFileChange } = formFields(dispatch, user.setProfileVal, account);
 
@@ -106,6 +109,13 @@ module.exports = function UserProfile() {
 				<label htmlFor="locked">Manually approve follow requests?</label>
 				<input id="locked" type="checkbox" checked={account.locked} onChange={onCheckChange("locked")} />
 			</div>
+			{ !allowCustomCSS ? null :  
+				<div className="labelinput">
+					<label htmlFor="customcss">Custom CSS</label>
+					<textarea className="mono" id="customcss" value={account.custom_css} onChange={onTextChange("custom_css")}/>
+					<a href="https://docs.gotosocial.org/en/latest/user_guide/custom_css" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about custom CSS (opens in a new tab)</a>
+				</div>
+			}
 			<Submit onClick={submit} label="Save profile info" errorMsg={errorMsg} statusMsg={statusMsg} />
 		</div>
 	);