diff --git a/apps/sim/app/workspace/[workspaceId]/components/find-bar/find-bar.tsx b/apps/sim/app/workspace/[workspaceId]/components/find-bar/find-bar.tsx
index ceb2b0b709b..6ec4de4377e 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/find-bar/find-bar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/components/find-bar/find-bar.tsx
@@ -129,7 +129,8 @@ export const FindBar = memo(function FindBar({
type='button'
variant='ghost'
size='icon'
- className='size-6 shrink-0'
+ iconSize='compact'
+ className='shrink-0'
aria-label={showReplace ? 'Hide replace' : 'Show replace'}
aria-expanded={showReplace}
onClick={() => setShowReplace((visible) => !visible)}
@@ -179,7 +180,8 @@ export const FindBar = memo(function FindBar({
type='button'
variant='ghost'
size='icon'
- className='size-6 shrink-0'
+ iconSize='compact'
+ className='shrink-0'
aria-label='Previous match'
title='Previous match (Shift+Enter)'
disabled={!navEnabled}
@@ -191,7 +193,8 @@ export const FindBar = memo(function FindBar({
type='button'
variant='ghost'
size='icon'
- className='size-6 shrink-0'
+ iconSize='compact'
+ className='shrink-0'
aria-label='Next match'
title='Next match (Enter)'
disabled={!navEnabled}
@@ -203,7 +206,8 @@ export const FindBar = memo(function FindBar({
type='button'
variant='ghost'
size='icon'
- className='size-6 shrink-0'
+ iconSize='compact'
+ className='shrink-0'
aria-label='Close find'
title='Close (Esc)'
onClick={onClose}
diff --git a/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.test.tsx b/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.test.tsx
index 0f57c5c0551..e2aa9d94339 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.test.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.test.tsx
@@ -1,7 +1,7 @@
/**
* @vitest-environment jsdom
*/
-import { act, type ReactNode } from 'react'
+import { act, type ComponentProps, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -19,6 +19,11 @@ vi.mock('next/navigation', () => ({
}))
vi.mock('@sim/emcn', () => ({
+ Button: ({
+ variant,
+ iconSize,
+ ...props
+ }: ComponentProps<'button'> & { variant?: string; iconSize?: string }) => ,
Check: () => null,
Duplicate: () => null,
Split: () => null,
diff --git a/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx b/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx
index 6207a1e247a..28780a945dd 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx
@@ -2,6 +2,7 @@
import { memo, useEffect, useRef, useState } from 'react'
import {
+ Button,
Check,
ChipModal,
ChipModalBody,
@@ -27,8 +28,6 @@ import { useForkMothershipChat } from '@/hooks/queries/mothership-chats'
import { useFolderStore } from '@/stores/folders/store'
const ICON_CLASS = 'size-[14px]'
-const BUTTON_CLASS =
- 'flex size-[26px] items-center justify-center rounded-[6px] text-[var(--text-icon)] transition-colors hover-hover:bg-[var(--surface-hover)] focus-visible:outline-hidden'
interface MessageActionsProps {
content: string
@@ -162,14 +161,15 @@ export const MessageActions = memo(function MessageActions({
{canCopyContent && (
-
+
{copied ? 'Copied message' : 'Copy message'}
@@ -180,27 +180,29 @@ export const MessageActions = memo(function MessageActions({
<>
-
+
Good response
-
+
Bad response
@@ -209,15 +211,16 @@ export const MessageActions = memo(function MessageActions({
{canFork && (
-
+
Fork in new chat
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-find-bar.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-find-bar.tsx
index a3f9e23198d..8313c2c549e 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-find-bar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-find-bar.tsx
@@ -106,7 +106,8 @@ export function BrowserFindBar({ inputRef, onClose, scopeId }: BrowserFindBarPro
size='sm'
aria-label='Previous match'
disabled={!result?.matches}
- className='size-[24px] shrink-0 p-0'
+ iconSize='compact-fixed'
+ className='shrink-0'
onClick={() => step('back')}
>
@@ -117,7 +118,8 @@ export function BrowserFindBar({ inputRef, onClose, scopeId }: BrowserFindBarPro
size='sm'
aria-label='Next match'
disabled={!result?.matches}
- className='size-[24px] shrink-0 p-0'
+ iconSize='compact-fixed'
+ className='shrink-0'
onClick={() => step('forward')}
>
@@ -127,7 +129,8 @@ export function BrowserFindBar({ inputRef, onClose, scopeId }: BrowserFindBarPro
variant='ghost-secondary'
size='sm'
aria-label='Close find bar'
- className='size-[24px] shrink-0 p-0'
+ iconSize='compact-fixed'
+ className='shrink-0'
onClick={dismiss}
>
diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx
index 18b04cfd5b2..2c53382bf54 100644
--- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/workflow-sidebar/workflow-sidebar.tsx
@@ -730,7 +730,8 @@ export function WorkflowSidebarBody({
'noopener,noreferrer'
)
}
- className='absolute right-[6px] bottom-1.5 z-10 size-[24px] cursor-pointer border border-[var(--border)] bg-[var(--surface-2)] p-0 hover-hover:bg-[var(--surface-4)]'
+ iconSize='compact-fixed'
+ className='absolute right-[6px] bottom-1.5 z-10 cursor-pointer border border-[var(--border)] bg-[var(--surface-2)] hover-hover:bg-[var(--surface-4)]'
>
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx
index c42c236d1c3..4bd3f1c6c00 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx
@@ -36,7 +36,7 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
const DEFAULT_DUPLICATE_OFFSET = { x: 50, y: 50 }
const ACTION_BUTTON_STYLES = [
- 'size-[24px] rounded-md p-0',
+ 'rounded-md',
'border-none bg-transparent text-[var(--text-icon)]',
'hover-hover:bg-[var(--surface-5)] hover-hover:text-[var(--text-primary)]!',
'dark:hover-hover:bg-[var(--surface-4)]',
@@ -472,6 +472,7 @@ export const ActionBar = memo(
}
if (canRunBlock) handleRunFromBlockClick()
}}
+ iconSize='compact-fixed'
className={cn(getActionButtonStyles('run'), isWorkflowRunning && 'group/run')}
disabled={!canStopWorkflow && !canRunBlock}
>
@@ -517,6 +518,7 @@ export const ActionBar = memo(
collaborativeBatchToggleBlockEnabled([blockId])
}
}}
+ iconSize='compact-fixed'
className={getActionButtonStyles('enabled')}
disabled={
isWorkflowRunning ||
@@ -553,6 +555,7 @@ export const ActionBar = memo(
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx
index 6a1ce331611..a583115eaa6 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx
@@ -1398,7 +1398,8 @@ function PreviewEditorContent({
type='button'
variant='ghost'
onClick={handleExpandChildWorkflow}
- className='absolute right-[6px] bottom-1.5 z-10 size-[24px] cursor-pointer border border-[var(--border)] bg-[var(--surface-2)] p-0 hover-hover:bg-[var(--surface-4)]'
+ iconSize='compact-fixed'
+ className='absolute right-[6px] bottom-1.5 z-10 cursor-pointer border border-[var(--border)] bg-[var(--surface-2)] hover-hover:bg-[var(--surface-4)]'
>
{isExecutionMode && onDrillDown ? (
diff --git a/packages/emcn/src/components/bulk-action-button/bulk-action-button.tsx b/packages/emcn/src/components/bulk-action-button/bulk-action-button.tsx
index b24e402e274..8088e98f093 100644
--- a/packages/emcn/src/components/bulk-action-button/bulk-action-button.tsx
+++ b/packages/emcn/src/components/bulk-action-button/bulk-action-button.tsx
@@ -18,7 +18,8 @@ export const bulkActionButtonVariants = cva(
}
)
-export interface BulkActionButtonProps extends Omit {
+export interface BulkActionButtonProps
+ extends Omit {
/** Accessible name for the icon action; tooltip content is supplied separately. */
'aria-label': string
/**
diff --git a/packages/emcn/src/components/button/button.test.tsx b/packages/emcn/src/components/button/button.test.tsx
new file mode 100644
index 00000000000..c0b4fc35df3
--- /dev/null
+++ b/packages/emcn/src/components/button/button.test.tsx
@@ -0,0 +1,67 @@
+/** @vitest-environment node */
+/** biome-ignore assist/source/organizeImports: Preserve the documented core/external/UI import order. */
+import { renderToStaticMarkup } from 'react-dom/server'
+import { describe, expect, it } from 'vitest'
+import { Button } from '@sim/emcn'
+
+function normalizeClasses(markup: string) {
+ return markup.replace(
+ /class="([^"]+)"/g,
+ (_, classes: string) => `class="${classes.split(/\s+/).sort().join(' ')}"`
+ )
+}
+
+/** The three existing Button treatments used by compact product actions. */
+const TREATMENTS = [
+ { size: undefined, variant: 'ghost' },
+ { size: 'sm', variant: 'ghost-secondary' },
+ { size: 'icon', variant: 'ghost' },
+] as const
+
+describe('Button iconSize', () => {
+ for (const [iconSize, previousClass] of [
+ ['compact', 'size-6 p-0'],
+ ['compact-fixed', 'size-[24px] p-0'],
+ ] as const) {
+ it.each(TREATMENTS)(
+ `preserves the ${iconSize} treatment with size=$size and variant=$variant`,
+ (treatment) => {
+ const icon =
+ const before = renderToStaticMarkup(
+
+ )
+ const after = renderToStaticMarkup(
+
+ )
+ expect(normalizeClasses(after)).toBe(normalizeClasses(before))
+ }
+ )
+ }
+
+ it('allows explicit padding and consumer width to take precedence', () => {
+ const markup = renderToStaticMarkup(
+
+ )
+ expect(markup).toContain('size-[24px]')
+ expect(markup).toContain('w-[40px]!')
+ expect(markup).toContain('p-1')
+ expect(markup).not.toContain('p-0')
+ expect(markup).not.toContain('iconSize')
+ expect(markup).not.toContain('iconPadding')
+ })
+})
diff --git a/packages/emcn/src/components/button/button.tsx b/packages/emcn/src/components/button/button.tsx
index 4ad21bc4a37..bc2d2c6ef66 100644
--- a/packages/emcn/src/components/button/button.tsx
+++ b/packages/emcn/src/components/button/button.tsx
@@ -50,6 +50,10 @@ const buttonVariants = cva(
md: 'px-2 py-1.5 text-[length:12px]',
icon: 'size-[20px] rounded-sm p-0 [&_svg]:[stroke-width:1.25]',
},
+ iconSize: {
+ compact: 'size-6 p-0',
+ 'compact-fixed': 'size-[24px] p-0',
+ },
iconPadding: {
sm: 'p-1',
md: 'p-1.5',
@@ -75,6 +79,15 @@ const buttonVariants = cva(
export interface ButtonProps
extends ButtonHTMLAttributes,
VariantProps {
+ /**
+ * Square icon-action geometry without changing the selected size's typography,
+ * corner radius, icon stroke or color. `compact` follows the spacing scale
+ * (24px at the default root font size); `compact-fixed` stays at 24px.
+ * Both remove padding; an explicit iconPadding or className can override it.
+ * Omit to retain the selected size's geometry.
+ * @example
+ */
+ iconSize?: VariantProps['iconSize']
/**
* Symmetric padding for icon actions whose content or layout determines their size.
* Preserves the selected size's typography, corner radius and icon stroke.
@@ -85,11 +98,11 @@ export interface ButtonProps
}
const Button = forwardRef(
- ({ className, variant, size, iconPadding, ...props }, ref) => {
+ ({ className, variant, size, iconSize, iconPadding, ...props }, ref) => {
return (
)
diff --git a/packages/emcn/src/components/composer-action-button/composer-action-button.tsx b/packages/emcn/src/components/composer-action-button/composer-action-button.tsx
index 806f128a179..0a466fc0544 100644
--- a/packages/emcn/src/components/composer-action-button/composer-action-button.tsx
+++ b/packages/emcn/src/components/composer-action-button/composer-action-button.tsx
@@ -26,7 +26,8 @@ export const composerActionButtonVariants = cva('rounded-full p-0', {
defaultVariants: { size: 'md', active: true },
})
-export interface ComposerActionButtonProps extends Omit {
+export interface ComposerActionButtonProps
+ extends Omit {
/** Accessible name for the caller's icon action. */
'aria-label': string
/** 28px by default; `sm` retains compact chat's 22px geometry and hover treatment. */
diff --git a/packages/workflow-renderer/src/note/note-block-view.tsx b/packages/workflow-renderer/src/note/note-block-view.tsx
index d47a556d391..6b71def6fb5 100644
--- a/packages/workflow-renderer/src/note/note-block-view.tsx
+++ b/packages/workflow-renderer/src/note/note-block-view.tsx
@@ -1058,7 +1058,8 @@ export function NoteBlockView({
event.stopPropagation()
onExpandedChange(!isExpanded)
}}
- className='nodrag nopan nowheel pointer-events-none ml-1 size-[24px] shrink-0 rounded-md border-none bg-transparent p-0 text-current opacity-0 transition-[background-color,color,opacity,transform] duration-150 hover-hover:bg-current/10 hover-hover:opacity-100 active:scale-[0.96] group-hover:pointer-events-auto group-hover:opacity-70 group-data-[node-selected]:pointer-events-auto group-data-[node-selected]:opacity-70'
+ iconSize='compact-fixed'
+ className='nodrag nopan nowheel pointer-events-none ml-1 shrink-0 rounded-md border-none bg-transparent text-current opacity-0 transition-[background-color,color,opacity,transform] duration-150 hover-hover:bg-current/10 hover-hover:opacity-100 active:scale-[0.96] group-hover:pointer-events-auto group-hover:opacity-70 group-data-[node-selected]:pointer-events-auto group-data-[node-selected]:opacity-70'
>