diff --git a/tgui/packages/tgui/interfaces/SelectEquipment.jsx b/tgui/packages/tgui/interfaces/SelectEquipment.jsx
new file mode 100644
index 00000000000..b7017a92236
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/SelectEquipment.jsx
@@ -0,0 +1,218 @@
+import { filter, map, sortBy, uniq } from 'common/collections';
+import { flow } from 'common/fp';
+import { createSearch } from 'common/string';
+
+import { useBackend, useLocalState } from '../backend';
+import { Box, Button, Icon, Input, Section, Stack, Tabs } from '../components';
+import { Window } from '../layouts';
+
+// here's an important mental define:
+// custom outfits give a ref keyword instead of path
+const getOutfitKey = (outfit) => outfit.path || outfit.ref;
+
+const useOutfitTabs = (categories) => {
+ return useLocalState('selected-tab', categories[0]);
+};
+
+export const SelectEquipment = (props) => {
+ const { act, data } = useBackend();
+ const { name, icon64, current_outfit, favorites } = data;
+
+ const isFavorited = (entry) => favorites?.includes(entry.path);
+
+ const outfits = map((entry) => ({
+ ...entry,
+ favorite: isFavorited(entry),
+ }))([...data.outfits, ...data.custom_outfits]);
+
+ // even if no custom outfits were sent, we still want to make sure there's
+ // at least a 'Custom' tab so the button to create a new one pops up
+ const categories = uniq([
+ ...outfits.map((entry) => entry.category),
+ 'Custom',
+ ]);
+ const [tab] = useOutfitTabs(categories);
+
+ const [searchText, setSearchText] = useLocalState('searchText', '');
+ const searchFilter = createSearch(
+ searchText,
+ (entry) => entry.name + entry.path,
+ );
+
+ const visibleOutfits = flow([
+ filter((entry) => entry.category === tab),
+ filter(searchFilter),
+ sortBy(
+ (entry) => !entry.favorite,
+ (entry) => !entry.priority,
+ (entry) => entry.name,
+ ),
+ ])(outfits);
+
+ const getOutfitEntry = (current_outfit) =>
+ outfits.find((outfit) => getOutfitKey(outfit) === current_outfit);
+
+ const currentOutfitEntry = getOutfitEntry(current_outfit);
+
+ return (
+
+
+
+
+
+
+ setSearchText(value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const DisplayTabs = (props) => {
+ const { categories } = props;
+ const [tab, setTab] = useOutfitTabs(categories);
+ return (
+
+ {categories.map((category) => (
+ setTab(category)}
+ >
+ {category}
+
+ ))}
+
+ );
+};
+
+const OutfitDisplay = (props) => {
+ const { act, data } = useBackend();
+ const { current_outfit } = data;
+ const { entries, currentTab } = props;
+ return (
+
+ {entries.map((entry) => (
+
+ );
+};
+
+const CurrentlySelectedDisplay = (props) => {
+ const { act, data } = useBackend();
+ const { current_outfit } = data;
+ const { entry } = props;
+ return (
+
+ {entry?.path && (
+
+
+ act('togglefavorite', {
+ path: entry.path,
+ })
+ }
+ />
+
+ )}
+
+ Currently selected:
+
+ {entry?.name}
+
+
+
+
+ act('applyoutfit', {
+ path: current_outfit,
+ })
+ }
+ >
+ Confirm
+
+
+
+ );
+};