diff --git a/apps/sim/app/(auth)/components/auth-nav-prompt.tsx b/apps/sim/app/(auth)/components/auth-nav-prompt.tsx
index d479a273b4a..bdea32fa50f 100644
--- a/apps/sim/app/(auth)/components/auth-nav-prompt.tsx
+++ b/apps/sim/app/(auth)/components/auth-nav-prompt.tsx
@@ -19,7 +19,7 @@ export function AuthNavPrompt({ prompt, href, linkLabel, onNavigate }: AuthNavPr
return (
{prompt && {prompt} }
-
+
{linkLabel}
diff --git a/apps/sim/app/(auth)/components/auth-submit-button.tsx b/apps/sim/app/(auth)/components/auth-submit-button.tsx
index 5d37ecb12ea..139bec31892 100644
--- a/apps/sim/app/(auth)/components/auth-submit-button.tsx
+++ b/apps/sim/app/(auth)/components/auth-submit-button.tsx
@@ -1,6 +1,5 @@
import type { ReactNode } from 'react'
import { Chip, Loader } from '@sim/emcn'
-import { AUTH_BUTTON_CLASS } from '@/app/(auth)/components/constants'
interface AuthSubmitButtonProps {
children: ReactNode
@@ -32,7 +31,8 @@ export function AuthSubmitButton({
onClick={onClick}
disabled={disabled || loading}
fullWidth
- className={AUTH_BUTTON_CLASS}
+ size='lg'
+ align='center'
>
{loading ? (
diff --git a/apps/sim/app/(auth)/components/constants.ts b/apps/sim/app/(auth)/components/constants.ts
deleted file mode 100644
index beb515c525d..00000000000
--- a/apps/sim/app/(auth)/components/constants.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * Auth and invite surfaces use a slightly taller control than the 30px chip
- * default, matching the landing `HeroCta` field family (the landing's own
- * auth-adjacent CTA renders taller fields than in-app chips). Applied as the
- * single source of truth for every auth field and button height so the inputs,
- * submit, social, SSO, and invite action buttons stay on one line.
- */
-export const AUTH_CONTROL_HEIGHT = 'h-9'
-
-/**
- * Shared layout for full-width auth/invite chip buttons (submit, social, SSO,
- * invite actions). `[&>span]:flex-none` collapses the chip's stretching label
- * span — which carries `flex-1` — so the icon + label cluster truly centers
- * under `justify-center` (the landing `HeroCta` idiom). Height-only inputs use
- * {@link AUTH_CONTROL_HEIGHT}; buttons compose this on top of it.
- */
-export const AUTH_BUTTON_CLASS = `${AUTH_CONTROL_HEIGHT} justify-center [&>span]:flex-none`
diff --git a/apps/sim/app/(auth)/components/password-input.tsx b/apps/sim/app/(auth)/components/password-input.tsx
index f62e9c23ad3..e1c4867f24e 100644
--- a/apps/sim/app/(auth)/components/password-input.tsx
+++ b/apps/sim/app/(auth)/components/password-input.tsx
@@ -1,11 +1,10 @@
'use client'
import { useState } from 'react'
-import { ChipInput, type ChipInputProps, cn } from '@sim/emcn'
+import { ChipInput, type ChipInputProps } from '@sim/emcn'
import { Eye, EyeOff } from '@sim/emcn/icons'
-import { AUTH_CONTROL_HEIGHT } from '@/app/(auth)/components/constants'
-type PasswordInputProps = Omit
+type PasswordInputProps = Omit
/**
* A {@link ChipInput} that owns the password reveal toggle — the eye button is
@@ -19,7 +18,8 @@ export function PasswordInput({ error, className, ...props }: PasswordInputProps
return (
@@ -85,7 +86,9 @@ export function SocialLoginButtons({
@@ -97,7 +100,9 @@ export function SocialLoginButtons({
diff --git a/apps/sim/app/(auth)/components/sso-login-button.tsx b/apps/sim/app/(auth)/components/sso-login-button.tsx
index 1bbd06d5591..03ac768b86f 100644
--- a/apps/sim/app/(auth)/components/sso-login-button.tsx
+++ b/apps/sim/app/(auth)/components/sso-login-button.tsx
@@ -1,8 +1,7 @@
'use client'
-import { Chip, cn } from '@sim/emcn'
+import { Chip } from '@sim/emcn'
import { useRouter } from 'next/navigation'
import { isSsoEnabled } from '@/lib/core/config/env-flags'
-import { AUTH_BUTTON_CLASS } from '@/app/(auth)/components/constants'
interface SSOLoginButtonProps {
callbackURL?: string
@@ -28,14 +27,12 @@ export function SSOLoginButton({
return (
Sign in with SSO
diff --git a/apps/sim/app/(auth)/oauth/consent/consent-view.tsx b/apps/sim/app/(auth)/oauth/consent/consent-view.tsx
index b083ef34f92..460cecf107d 100644
--- a/apps/sim/app/(auth)/oauth/consent/consent-view.tsx
+++ b/apps/sim/app/(auth)/oauth/consent/consent-view.tsx
@@ -14,7 +14,6 @@ import {
AuthSubmitButton,
AuthTextLink,
} from '@/app/(auth)/components'
-import { AUTH_BUTTON_CLASS } from '@/app/(auth)/components/constants'
import { OAuthConsentLoading } from '@/app/(auth)/oauth/consent/loading'
import {
useOAuthConsent,
@@ -166,7 +165,8 @@ export function OAuthConsentView({
variant='border'
fullWidth
disabled={isPending}
- className={AUTH_BUTTON_CLASS}
+ size='lg'
+ align='center'
onClick={() => decide(false)}
>
{consent.isPending && consent.variables === false ? 'Declining…' : 'Deny'}
diff --git a/apps/sim/app/(interfaces)/chat/components/auth/email/email-auth.tsx b/apps/sim/app/(interfaces)/chat/components/auth/email/email-auth.tsx
index 7972106bfc9..2781d70779b 100644
--- a/apps/sim/app/(interfaces)/chat/components/auth/email/email-auth.tsx
+++ b/apps/sim/app/(interfaces)/chat/components/auth/email/email-auth.tsx
@@ -143,7 +143,7 @@ export default function EmailAuth({ identifier }: EmailAuthProps) {
autoCorrect='off'
value={email}
onChange={handleEmailChange}
- className='h-[34px]'
+ size='lg'
error={Boolean(hasEmailError)}
/>
{hasEmailError && (
diff --git a/apps/sim/app/(interfaces)/chat/components/auth/password/password-auth.tsx b/apps/sim/app/(interfaces)/chat/components/auth/password/password-auth.tsx
index cd49a26fa28..36467bc6287 100644
--- a/apps/sim/app/(interfaces)/chat/components/auth/password/password-auth.tsx
+++ b/apps/sim/app/(interfaces)/chat/components/auth/password/password-auth.tsx
@@ -72,7 +72,6 @@ export default function PasswordAuth({ identifier }: PasswordAuthProps) {
placeholder='Enter password'
value={password}
onChange={handlePasswordChange}
- className='h-[34px]'
error={hasPasswordError}
/>
{error ? {error}
: null}
diff --git a/apps/sim/app/f/[token]/public-file-email-auth.tsx b/apps/sim/app/f/[token]/public-file-email-auth.tsx
index 8d237ed9ff5..a674db4ffcb 100644
--- a/apps/sim/app/f/[token]/public-file-email-auth.tsx
+++ b/apps/sim/app/f/[token]/public-file-email-auth.tsx
@@ -104,7 +104,7 @@ export function PublicFileEmailAuth({ token }: PublicFileEmailAuthProps) {
setEmail(e.target.value)
setError(null)
}}
- className='h-[34px]'
+ size='lg'
error={Boolean(error)}
/>
{error ? {error}
: null}
diff --git a/apps/sim/app/f/[token]/public-file-sso-auth.tsx b/apps/sim/app/f/[token]/public-file-sso-auth.tsx
index 26ca1961483..20b38e83579 100644
--- a/apps/sim/app/f/[token]/public-file-sso-auth.tsx
+++ b/apps/sim/app/f/[token]/public-file-sso-auth.tsx
@@ -82,7 +82,7 @@ export function PublicFileSSOAuth({ token }: PublicFileSSOAuthProps) {
setEmail(e.target.value)
setError(null)
}}
- className='h-[34px]'
+ size='lg'
error={Boolean(error)}
/>
{error ? {error}
: null}
diff --git a/apps/sim/app/invite/components/index.ts b/apps/sim/app/invite/components/index.ts
index f72433bf8a8..817d145aaae 100644
--- a/apps/sim/app/invite/components/index.ts
+++ b/apps/sim/app/invite/components/index.ts
@@ -1,4 +1,5 @@
export { InvitationDisclosure } from '@/app/invite/components/invitation-disclosure'
export { InvitationWorkspaceAccess } from '@/app/invite/components/invitation-workspace-access'
+export { InviteHeading } from '@/app/invite/components/invite-heading'
export { default as InviteLayout } from '@/app/invite/components/layout'
export { InviteStatusCard } from '@/app/invite/components/status-card'
diff --git a/apps/sim/app/invite/components/invite-heading.tsx b/apps/sim/app/invite/components/invite-heading.tsx
new file mode 100644
index 00000000000..c2b54626498
--- /dev/null
+++ b/apps/sim/app/invite/components/invite-heading.tsx
@@ -0,0 +1,16 @@
+import type { ReactNode } from 'react'
+
+interface InviteHeadingProps {
+ title: ReactNode
+ children: ReactNode
+}
+
+/** Heading shared by invitation and email-preference states; subcopy stays with each caller. */
+export function InviteHeading({ title, children }: InviteHeadingProps) {
+ return (
+
+
{title}
+ {children}
+
+ )
+}
diff --git a/apps/sim/app/invite/components/status-card.tsx b/apps/sim/app/invite/components/status-card.tsx
index 282ec08d7d1..403e64fdb1e 100644
--- a/apps/sim/app/invite/components/status-card.tsx
+++ b/apps/sim/app/invite/components/status-card.tsx
@@ -1,7 +1,7 @@
'use client'
-import { Chip, cn, Loader } from '@sim/emcn'
+import { Chip, Loader } from '@sim/emcn'
import { AuthSubmitButton } from '@/app/(auth)/components'
-import { AUTH_BUTTON_CLASS } from '@/app/(auth)/components/constants'
+import { InviteHeading } from '@/app/invite/components/invite-heading'
/** A document navigation, so the marketing surface initializes its own theme store. */
function returnHome(): void {
@@ -37,10 +37,9 @@ export function InviteStatusCard({
if (type === 'loading') {
return (
<>
-
-
Loading
+
{description}
-
+
@@ -50,10 +49,9 @@ export function InviteStatusCard({
return (
<>
-
-
{title}
+
{description}
-
+
{details}
@@ -81,7 +79,9 @@ export function InviteStatusCard({
fullWidth
onClick={action.onClick}
disabled={action.disabled || action.loading}
- className={cn(AUTH_BUTTON_CLASS, 'border border-[var(--border)]')}
+ variant='outline'
+ size='lg'
+ align='center'
>
{action.loading ? (
diff --git a/apps/sim/app/unsubscribe/unsubscribe.tsx b/apps/sim/app/unsubscribe/unsubscribe.tsx
index 84004b7827a..34083d2b4ee 100644
--- a/apps/sim/app/unsubscribe/unsubscribe.tsx
+++ b/apps/sim/app/unsubscribe/unsubscribe.tsx
@@ -1,13 +1,12 @@
'use client'
import { Suspense } from 'react'
-import { Chip, cn, Loader } from '@sim/emcn'
+import { Chip, Loader } from '@sim/emcn'
import { getErrorMessage } from '@sim/utils/errors'
import { useSearchParams } from 'next/navigation'
import type { UnsubscribeType } from '@/lib/api/contracts/user'
import { AuthSubmitButton } from '@/app/(auth)/components'
-import { AUTH_BUTTON_CLASS } from '@/app/(auth)/components/constants'
-import { InviteLayout } from '@/app/invite/components'
+import { InviteHeading, InviteLayout } from '@/app/invite/components'
import { useUnsubscribe, useUnsubscribeMutation } from '@/hooks/queries/unsubscribe'
function UnsubscribeContent() {
@@ -39,10 +38,9 @@ function UnsubscribeContent() {
if (loading) {
return (
-
-
Loading
+
Validating your unsubscribe link…
-
+
@@ -53,12 +51,9 @@ function UnsubscribeContent() {
if (error) {
return (
-
-
- Invalid Unsubscribe Link
-
+
{error}
-
+
window.history.back()} loadingLabel=''>
@@ -72,15 +67,12 @@ function UnsubscribeContent() {
if (data?.isTransactional) {
return (
-
-
- Important Account Emails
-
+
Transactional emails like password resets, account confirmations, and security alerts
cannot be unsubscribed from as they contain essential information for your account.
-
+
window.close()} loadingLabel=''>
@@ -94,15 +86,12 @@ function UnsubscribeContent() {
if (unsubscribed) {
return (
-
-
- Successfully Unsubscribed
-
+
You have been unsubscribed from our emails. You will stop receiving emails within 48
hours.
-
+
window.close()} loadingLabel=''>
@@ -117,15 +106,12 @@ function UnsubscribeContent() {
return (
-
-
- Email Preferences
-
+
Choose which emails you'd like to stop receiving.
{data?.email}
-
+
{data?.currentPreferences.unsubscribeMarketing
? 'Unsubscribed from Marketing'
@@ -167,7 +155,9 @@ function UnsubscribeContent() {
isAlreadyUnsubscribedFromAll ||
data?.currentPreferences.unsubscribeUpdates
}
- className={cn(AUTH_BUTTON_CLASS, 'border border-[var(--border-1)]')}
+ variant='outline'
+ size='lg'
+ align='center'
>
{data?.currentPreferences.unsubscribeUpdates
? 'Unsubscribed from Updates'
@@ -182,7 +172,9 @@ function UnsubscribeContent() {
isAlreadyUnsubscribedFromAll ||
data?.currentPreferences.unsubscribeNotifications
}
- className={cn(AUTH_BUTTON_CLASS, 'border border-[var(--border-1)]')}
+ variant='outline'
+ size='lg'
+ align='center'
>
{data?.currentPreferences.unsubscribeNotifications
? 'Unsubscribed from Notifications'
@@ -205,10 +197,9 @@ export default function Unsubscribe() {
-
-
Loading
+
Validating your unsubscribe link…
-
+
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/slack-setup-wizard/slack-setup-wizard.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/slack-setup-wizard/slack-setup-wizard.tsx
index 4ffb44d9739..f2d0fb0d001 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/slack-setup-wizard/slack-setup-wizard.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/slack-setup-wizard/slack-setup-wizard.tsx
@@ -226,7 +226,7 @@ function StepConfigure({
onChange={(e) => onAppNameChange(e.target.value)}
disabled={disabled}
placeholder={DEFAULT_APP_NAME}
- className='h-9'
+ size='lg'
/>
@@ -416,7 +416,7 @@ function SecretField({ id, label, value, onChange, disabled, placeholder }: Secr
onChange={onChange}
disabled={disabled}
placeholder={placeholder}
- className='h-9'
+ size='lg'
/>
)
diff --git a/apps/sim/ee/sso/components/sso-auth.tsx b/apps/sim/ee/sso/components/sso-auth.tsx
index 855651edaed..0b8deb58c9e 100644
--- a/apps/sim/ee/sso/components/sso-auth.tsx
+++ b/apps/sim/ee/sso/components/sso-auth.tsx
@@ -127,7 +127,7 @@ export default function SSOAuth({ identifier }: SSOAuthProps) {
value={email}
onChange={handleEmailChange}
onKeyDown={handleKeyDown}
- className='h-[34px]'
+ size='lg'
error={showEmailValidationError && emailErrors.length > 0}
/>
{showEmailValidationError && emailErrors.length > 0 && (
diff --git a/apps/sim/ee/sso/components/sso-form.test.tsx b/apps/sim/ee/sso/components/sso-form.test.tsx
index 369ef6a165b..9f71416d717 100644
--- a/apps/sim/ee/sso/components/sso-form.test.tsx
+++ b/apps/sim/ee/sso/components/sso-form.test.tsx
@@ -1,7 +1,7 @@
/**
* @vitest-environment jsdom
*/
-import { act, type ButtonHTMLAttributes, type InputHTMLAttributes, type ReactNode } from 'react'
+import { act, type InputHTMLAttributes, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { renderToString } from 'react-dom/server'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -26,15 +26,16 @@ vi.mock('next/link', () => ({
}))
vi.mock('@sim/emcn', () => ({
- Button: ({ children, ...props }: ButtonHTMLAttributes) => (
-
- {children}
-
+ ChipLink: ({ href, children }: { href: string; children?: ReactNode }) => (
+ {children}
),
ChipInput: ({
error: _error,
+ size: _size,
...props
- }: InputHTMLAttributes & { error?: boolean }) => ,
+ }: Omit, 'size'> & { error?: boolean; size?: string }) => (
+
+ ),
Label: ({ children }: { children?: ReactNode }) => {children} ,
cn: (...values: unknown[]) => values.filter(Boolean).join(' '),
}))
diff --git a/apps/sim/ee/sso/components/sso-form.tsx b/apps/sim/ee/sso/components/sso-form.tsx
index 9d9f39d10d1..369de48c16d 100644
--- a/apps/sim/ee/sso/components/sso-form.tsx
+++ b/apps/sim/ee/sso/components/sso-form.tsx
@@ -1,7 +1,7 @@
'use client'
import { useEffect, useState } from 'react'
-import { Button, ChipInput, Label } from '@sim/emcn'
+import { ChipInput, ChipLink, Label } from '@sim/emcn'
import { createLogger } from '@sim/logger'
import Link from 'next/link'
import { useSearchParams } from 'next/navigation'
@@ -213,7 +213,7 @@ function SSOFormContent({
onChange={handleEmailChange}
aria-invalid={hasEmailError || undefined}
aria-describedby={hasEmailError ? 'sso-email-errors' : undefined}
- className='h-[34px]'
+ size='lg'
error={Boolean(hasEmailError)}
/>
{hasEmailError && (
@@ -247,13 +247,15 @@ function SSOFormContent({
-
-
- Sign in with email
-
-
+ Sign in with email
+
>
)}