diff --git a/web/source/settings-panel/components/form-fields.jsx b/web/source/settings-panel/components/form-fields.jsx
index a3b19bc17..30f209f13 100644
--- a/web/source/settings-panel/components/form-fields.jsx
+++ b/web/source/settings-panel/components/form-fields.jsx
@@ -71,7 +71,7 @@ function get(state, id) {
 
 module.exports = {
 	formFields: function formFields(setter, selector) {
-		function FormField({type, id, name, className="", placeHolder="", fileType="", children=null}) {
+		function FormField({type, id, name, className="", placeHolder="", fileType="", children=null, options={}}) {
 			const dispatch = Redux.useDispatch();
 			let state = Redux.useSelector(selector);
 			let {
@@ -88,6 +88,12 @@ module.exports = {
 				field = <textarea type="text" id={id} value={get(state, id)} placeholder={placeHolder} className={className} onChange={onTextChange(id)}/>;
 			} else if (type == "checkbox") {
 				field = <input type="checkbox" id={id} checked={get(state, id)} className={className} onChange={onCheckChange(id)}/>;
+			} else if (type == "select") {
+				field = (
+					<select id={id} checked={get(state, id)} className={className} onChange={onTextChange(id)}>
+						{options}
+					</select>
+				);
 			} else if (type == "file") {
 				defaultLabel = false;
 				let file = get(state, `${id}File`);
@@ -128,6 +134,10 @@ module.exports = {
 				return <FormField type="checkbox" {...props} />;
 			},
 	
+			Select: function(props) {
+				return <FormField type="select" {...props} />;
+			},
+	
 			File: function(props) {
 				return <FormField type="file" {...props} />;
 			},
diff --git a/web/source/settings-panel/lib/form-fields.js b/web/source/settings-panel/lib/form-fields.js
deleted file mode 100644
index dfdd93086..000000000
--- a/web/source/settings-panel/lib/form-fields.js
+++ /dev/null
@@ -1,50 +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";
-
-const d = require("dotty");
-
-module.exports = function(dispatch, setter, obj) {
-	return {
-		onTextChange: function (key) {
-			return function (e) {
-				dispatch(setter([key, e.target.value]));
-			};
-		},
-	
-		onCheckChange: function (key) {
-			return function (e) {
-				dispatch(setter([key, e.target.checked]));
-			};
-		},
-	
-		onFileChange: function (key) {
-			return function (e) {
-				let old = d.get(obj, key);
-				if (old != undefined) {
-					URL.revokeObjectURL(old); // no error revoking a non-Object URL as provided by instance
-				}
-				let file = e.target.files[0];
-				let objectURL = URL.createObjectURL(file);
-				dispatch(setter([key, objectURL]));
-				dispatch(setter([`${key}File`, file]));
-			};
-		}
-	};
-};
diff --git a/web/source/settings-panel/user/profile.js b/web/source/settings-panel/user/profile.js
index 05a1249c5..e06d3cde1 100644
--- a/web/source/settings-panel/user/profile.js
+++ b/web/source/settings-panel/user/profile.js
@@ -43,8 +43,6 @@ module.exports = function UserProfile() {
 
 	const allowCustomCSS = instance.configuration.accounts.allow_custom_css;
 
-	const { onTextChange, onCheckChange, onFileChange } = formFields(dispatch, user.setProfileVal, account);
-
 	const [errorMsg, setError] = React.useState("");
 	const [statusMsg, setStatus] = React.useState("");
 
diff --git a/web/source/settings-panel/user/security.js b/web/source/settings-panel/user/security.js
deleted file mode 100644
index 0d9fe63c3..000000000
--- a/web/source/settings-panel/user/security.js
+++ /dev/null
@@ -1,26 +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";
-
-const React = require("react");
-const Promise = require("bluebird");
-const Redux = require("react-redux");
-
-const Submit = require("../../lib/submit");
-const api = require("../../lib");
diff --git a/web/source/settings-panel/user/settings.js b/web/source/settings-panel/user/settings.js
index ac023254e..48a8ff241 100644
--- a/web/source/settings-panel/user/settings.js
+++ b/web/source/settings-panel/user/settings.js
@@ -23,17 +23,18 @@ const React = require("react");
 const Redux = require("react-redux");
 
 const api = require("../lib/api");
-const formFields = require("../lib/form-fields");
 const user = require("../redux/reducers/user").actions;
 
 const Languages = require("../components/languages");
 const Submit = require("../components/submit");
 
+const {
+	Checkbox,
+	Select,
+} = require("../components/form-fields").formFields(user.setProfileVal, (state) => state.user.profile);
+
 module.exports = function UserSettings() {
 	const dispatch = Redux.useDispatch();
-	const account = Redux.useSelector(state => state.user.settings);
-
-	const { onTextChange, onCheckChange } = formFields(dispatch, user.setSettingsVal, account);
 
 	const [errorMsg, setError] = React.useState("");
 	const [statusMsg, setStatus] = React.useState("");
@@ -54,42 +55,39 @@ module.exports = function UserSettings() {
 	return (
 		<div className="user-settings">
 			<h1>Post settings</h1>
-			<div className="labelselect">
-				<label htmlFor="language">Default post language</label>
-				<select id="language" autoComplete="language" value={account.source.language.toUpperCase()} onChange={onTextChange("source.language")}>
-					<Languages />
-				</select>
-			</div>
-			<div className="labelselect">
-				<label htmlFor="privacy">Default post privacy</label>
-				<select id="privacy" value={account.source.privacy} onChange={onTextChange("source.privacy")}>
+			<Select id="language" name="Default post language">
+				<Languages/>
+			</Select>
+			<Select id="privacy" name="Default post privacy" options={
+				<>
 					<option value="private">Private / followers-only)</option>
 					<option value="unlisted">Unlisted</option>
 					<option value="public">Public</option>
-				</select>
+				</>
+			}>
 				<a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#privacy-settings" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post privacy settings (opens in a new tab)</a>
-			</div>
-			<div className="labelselect">
-				<label htmlFor="format">Default post format</label>
-				<select id="format" value={account.source.format} onChange={onTextChange("source.format")}>
+			</Select>
+			<Select id="format" name="Default post format" options={
+				<>
 					<option value="plain">Plain (default)</option>
 					<option value="markdown">Markdown</option>
-				</select>
+				</>
+			}>
 				<a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#input-types" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post format settings (opens in a new tab)</a>
-			</div>				
-			<div className="labelcheckbox">
-				<label htmlFor="sensitive">Mark my posts as sensitive by default</label>
-				<input id="sensitive" type="checkbox" checked={account.source.sensitive} onChange={onCheckChange("source.sensitive")}/>
-			</div>
-			<Submit onClick={submit} label="Save post settings" errorMsg={errorMsg} statusMsg={statusMsg}/>
+			</Select>
+			<Checkbox
+				id="sensitive"
+				name="Mark my posts as sensitive by default"
+			/>
 
+			<Submit onClick={submit} label="Save post settings" errorMsg={errorMsg} statusMsg={statusMsg}/>
 			<hr/>
 			<PasswordChange/>
 		</div>
 	);
 };
 
-function PasswordChange({oauth}) {
+function PasswordChange() {
 	const dispatch = Redux.useDispatch();
 
 	const [errorMsg, setError] = React.useState("");