';
diff --git a/src/gui/src/UI/Dashboard/teamActionIcons.js b/src/gui/src/UI/Dashboard/teamActionIcons.js
new file mode 100644
index 000000000..79ba78daf
--- /dev/null
+++ b/src/gui/src/UI/Dashboard/teamActionIcons.js
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024-present Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter 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 .
+ */
+
+// A row carries up to five actions, and five labelled buttons do not fit.
+// Inline like the sidebar chevron: one-place glyphs, not shared assets.
+
+const SVG = (body) =>
+ ``;
+
+export const TEAM_ACTION_ICONS = {
+ /** Change plan — a card. */
+ plan: SVG(''),
+ /** Reissue credential — a key. */
+ credential: SVG(''),
+ /** Suspend — pause. */
+ suspend: SVG(''),
+ /** Re-enable — play. */
+ enable: SVG(''),
+ /** Delete the account — a bin. */
+ remove: SVG(''),
+ /** Edit the account — a pencil. */
+ edit: SVG(''),
+};
+
+/** The label stays as tooltip and screen-reader text, so nothing is lost. */
+export const teamActionButton = ({ className, icon, label, danger, attrs }) => {
+ const safeLabel = window.html_encode(label);
+ const extra = Object.entries(attrs ?? {})
+ .map(([k, v]) => ` ${k}="${window.html_encode(v ?? '')}"`)
+ .join('');
+ return (
+ ``
+ );
+};
+
+export default teamActionButton;
diff --git a/src/gui/src/UI/Dashboard/teamActionIcons.test.js b/src/gui/src/UI/Dashboard/teamActionIcons.test.js
new file mode 100644
index 000000000..6da17221c
--- /dev/null
+++ b/src/gui/src/UI/Dashboard/teamActionIcons.test.js
@@ -0,0 +1,66 @@
+import { describe, expect, it } from 'vitest';
+
+globalThis.window = { html_encode: (v) => String(v).replace(/[<"]/g, (c) => (c === '<' ? '<' : '"')) };
+
+const { teamActionButton, TEAM_ACTION_ICONS } = await import('./teamActionIcons.js');
+
+describe('an icon action button', () => {
+ it('keeps the label reachable without showing it', () => {
+ // Dropping the visible text must not drop it for a screen reader.
+ const h = teamActionButton({
+ className: 'teams-reset',
+ icon: 'credential',
+ label: 'Reissue password',
+ });
+ expect(h).toContain('title="Reissue password"');
+ expect(h).toContain('aria-label="Reissue password"');
+ expect(h).toContain('Reissue password');
+ });
+
+ it('carries the class the click handler binds to', () => {
+ const h = teamActionButton({ className: 'teams-disable', icon: 'suspend', label: 'x' });
+ expect(h).toContain('teams-disable');
+ });
+
+ it('passes through the data attributes an action needs', () => {
+ const h = teamActionButton({
+ className: 'teams-plan-change',
+ icon: 'plan',
+ label: 'Change plan',
+ attrs: { 'data-username': 'ana', 'data-uuid': 'u-1' },
+ });
+ expect(h).toContain('data-username="ana"');
+ expect(h).toContain('data-uuid="u-1"');
+ });
+
+ it('marks only the destructive ones', () => {
+ expect(teamActionButton({ className: 'a', icon: 'remove', label: 'x', danger: true }))
+ .toContain('button-danger');
+ expect(teamActionButton({ className: 'a', icon: 'plan', label: 'x' }))
+ .not.toContain('button-danger');
+ });
+
+ it('encodes a label and an attribute value', () => {
+ const h = teamActionButton({
+ className: 'a',
+ icon: 'plan',
+ label: '