diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-builder/filter-builder.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-builder/filter-builder.tsx
index 4df315eb4c4..a716ed258e3 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-builder/filter-builder.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-builder/filter-builder.tsx
@@ -1,8 +1,7 @@
'use client'
import { useCallback, useMemo } from 'react'
-import { Button } from '@sim/emcn'
-import { Plus } from '@sim/emcn/icons'
+import { AddRowButton } from '@sim/emcn'
import { useTableColumns } from '@/lib/table/hooks'
import type { FilterRule } from '@/lib/table/query-builder/constants'
import { useFilterBuilder } from '@/lib/table/query-builder/use-query-builder'
@@ -83,16 +82,7 @@ export function FilterBuilder({
if (rules.length === 0) {
if (isReadOnly) return null
- return (
-
- )
+ return Add filter condition
}
return (
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-builder/sort-builder.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-builder/sort-builder.tsx
index c9963668a00..d4092384e9d 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-builder/sort-builder.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-builder/sort-builder.tsx
@@ -1,8 +1,7 @@
'use client'
import { useCallback, useMemo } from 'react'
-import { Button, type ComboboxOption } from '@sim/emcn'
-import { Plus } from '@sim/emcn/icons'
+import { AddRowButton, type ComboboxOption } from '@sim/emcn'
import { generateId } from '@sim/utils/id'
import { useTableColumns } from '@/lib/table/hooks'
import { SORT_DIRECTION_OPTIONS, type SortRule } from '@/lib/table/query-builder/constants'
@@ -86,16 +85,7 @@ export function SortBuilder({
if (rules.length === 0) {
if (isReadOnly) return null
- return (
-
- )
+ return Add sort
}
return (
diff --git a/packages/emcn/src/components/add-row-button/add-row-button.test.tsx b/packages/emcn/src/components/add-row-button/add-row-button.test.tsx
new file mode 100644
index 00000000000..dfd6cb20e1f
--- /dev/null
+++ b/packages/emcn/src/components/add-row-button/add-row-button.test.tsx
@@ -0,0 +1,47 @@
+/** @vitest-environment jsdom */
+import { act, createRef } from 'react'
+import { AddRowButton } from '@sim/emcn'
+import { createRoot, type Root } from 'react-dom/client'
+import { afterEach, describe, expect, it, vi } from 'vitest'
+
+let root: Root | null = null
+let container: HTMLDivElement | null = null
+
+afterEach(() => {
+ if (root) act(() => root?.unmount())
+ container?.remove()
+ root = null
+ container = null
+})
+
+describe('AddRowButton', () => {
+ it('forwards its ref and action, honors disabled, and preserves native form submission', () => {
+ ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
+ container = document.createElement('div')
+ document.body.appendChild(container)
+ root = createRoot(container)
+ const ref = createRef()
+ const onClick = vi.fn()
+ const onSubmit = vi.fn((event) => event.preventDefault())
+ const render = (disabled: boolean) => (
+
+ )
+ act(() => root?.render(render(false)))
+ const button = ref.current!
+ expect(button).toBe(container.querySelector('button'))
+ expect(button.textContent).toBe('Add filter condition')
+ expect(button.querySelector('svg')?.getAttribute('aria-hidden')).toBe('true')
+ act(() => button.click())
+ expect(onClick).toHaveBeenCalledOnce()
+ expect(onSubmit).toHaveBeenCalledOnce()
+
+ act(() => root?.render(render(true)))
+ expect(button.disabled).toBe(true)
+ act(() => button.click())
+ expect(onClick).toHaveBeenCalledOnce()
+ })
+})
diff --git a/packages/emcn/src/components/add-row-button/add-row-button.tsx b/packages/emcn/src/components/add-row-button/add-row-button.tsx
new file mode 100644
index 00000000000..b34dedaa536
--- /dev/null
+++ b/packages/emcn/src/components/add-row-button/add-row-button.tsx
@@ -0,0 +1,32 @@
+import { type ComponentPropsWithoutRef, forwardRef } from 'react'
+import { Plus } from '../../icons/plus'
+import { cn } from '../../lib/cn'
+import { Button } from '../button/button'
+
+export type AddRowButtonProps = ComponentPropsWithoutRef<'button'>
+
+/**
+ * Full-width dashed action for adding the first row to an empty editor list.
+ * Keeps the existing 28px filter/sort treatment; list state stays with the caller.
+ * Forwards native button props and refs, preserving Button's native form behavior.
+ *
+ * @example Add filter condition
+ */
+export const AddRowButton = forwardRef(
+ ({ children, className, ...props }, ref) => (
+
+ )
+)
+
+AddRowButton.displayName = 'AddRowButton'
diff --git a/packages/emcn/src/components/index.ts b/packages/emcn/src/components/index.ts
index c7c2204968a..0848262daa5 100644
--- a/packages/emcn/src/components/index.ts
+++ b/packages/emcn/src/components/index.ts
@@ -1,3 +1,4 @@
+export { AddRowButton, type AddRowButtonProps } from './add-row-button/add-row-button'
export { Avatar, AvatarFallback, AvatarImage } from './avatar/avatar'
export { Badge, type BadgeProps } from './badge/badge'
export { Banner } from './banner/banner'