diff --git a/.server-changes/tailwind-v4-migration.md b/.server-changes/tailwind-v4-migration.md new file mode 100644 index 00000000000..c1675ff21b7 --- /dev/null +++ b/.server-changes/tailwind-v4-migration.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: improvement +--- + +Migrated the webapp to Tailwind CSS v4 with a CSS-first `@theme`. Semantic color tokens are now CSS variables overridable per theme (multi-theme support). Tailwind runs through `@tailwindcss/postcss`; plugins replaced or upgraded to v4-compatible versions. diff --git a/.server-changes/theme-color-tokens.md b/.server-changes/theme-color-tokens.md new file mode 100644 index 00000000000..1766a74d2f4 --- /dev/null +++ b/.server-changes/theme-color-tokens.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: improvement +--- + +Replaced raw charcoal-* Tailwind classes with semantic theme tokens (surfaces, borders, text) across the dashboard. Adds surface/border/text tokens to the themable layer so future themes only override CSS variables. diff --git a/apps/webapp/app/components/AskAI.tsx b/apps/webapp/app/components/AskAI.tsx index bf76654cced..d62ffa5b33d 100644 --- a/apps/webapp/app/components/AskAI.tsx +++ b/apps/webapp/app/components/AskAI.tsx @@ -173,7 +173,7 @@ function AskAIDialog({ initialQuery, isOpen, onOpenChange, closeAskAI }: AskAIDi return ( - +
Ask AI @@ -226,7 +226,7 @@ function ChatMessages({ ]; return ( -
+
{conversation.length === 0 ? ( ( onExampleClick(question)} variants={{ hidden: { @@ -468,7 +468,7 @@ function ChatInterface({ initialQuery }: { initialQuery?: string }) { error={error} addFeedback={addFeedback} /> -
+
} variant="primary/large" - className="size-10 min-w-10 rounded-full group-disabled/button:border-charcoal-550 group-disabled/button:bg-charcoal-600" + className="size-10 min-w-10 rounded-full group-disabled/button:border-border-brighter group-disabled/button:bg-surface-control" /> )}
@@ -531,11 +531,11 @@ function GradientSpinnerBackground({ }) { return (
{children} diff --git a/apps/webapp/app/components/BackgroundWrapper.tsx b/apps/webapp/app/components/BackgroundWrapper.tsx index aaf06d56aaf..c13983a12af 100644 --- a/apps/webapp/app/components/BackgroundWrapper.tsx +++ b/apps/webapp/app/components/BackgroundWrapper.tsx @@ -7,7 +7,7 @@ export function BackgroundWrapper({ children }: { children: ReactNode }) { return (
diff --git a/apps/webapp/app/components/DefinitionTooltip.tsx b/apps/webapp/app/components/DefinitionTooltip.tsx index d91cce92c99..13505e2d57f 100644 --- a/apps/webapp/app/components/DefinitionTooltip.tsx +++ b/apps/webapp/app/components/DefinitionTooltip.tsx @@ -17,11 +17,11 @@ export function DefinitionTip({ - + {children} - + {title} {typeof content === "string" ? ( {content} diff --git a/apps/webapp/app/components/Feedback.tsx b/apps/webapp/app/components/Feedback.tsx index 683f39d34ab..ebd61180de5 100644 --- a/apps/webapp/app/components/Feedback.tsx +++ b/apps/webapp/app/components/Feedback.tsx @@ -84,7 +84,7 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac Contact us
- + How can we help? We read every message and will respond as quickly as we can. diff --git a/apps/webapp/app/components/GitMetadata.tsx b/apps/webapp/app/components/GitMetadata.tsx index fb53ee6bfea..3057de10425 100644 --- a/apps/webapp/app/components/GitMetadata.tsx +++ b/apps/webapp/app/components/GitMetadata.tsx @@ -28,7 +28,7 @@ export function GitMetadataBranch({ leadingIconClassName="group-hover/table-row:text-text-bright" iconSpacing="gap-x-1" to={git.branchUrl} - className="pl-1 duration-0 [&_span]:duration-0 [&_span]:group-hover/table-row:text-text-bright" + className="pl-1 duration-0 [&_span]:duration-0 group-hover/table-row:[&_span]:text-text-bright" > {git.branchName} @@ -52,7 +52,7 @@ export function GitMetadataCommit({ LeadingIcon={} leadingIconClassName="group-hover/table-row:text-text-bright" iconSpacing="gap-x-1" - className="pl-1 duration-0 [&_span]:duration-0 [&_span]:group-hover/table-row:text-text-bright" + className="pl-1 duration-0 [&_span]:duration-0 group-hover/table-row:[&_span]:text-text-bright" > {`${git.shortSha} / ${git.commitMessage}`} @@ -78,7 +78,7 @@ export function GitMetadataPullRequest({ LeadingIcon={} leadingIconClassName="group-hover/table-row:text-text-bright" iconSpacing="gap-x-1" - className="pl-1 duration-0 [&_span]:duration-0 [&_span]:group-hover/table-row:text-text-bright" + className="pl-1 duration-0 [&_span]:duration-0 group-hover/table-row:[&_span]:text-text-bright" > #{git.pullRequestNumber} {git.pullRequestTitle} diff --git a/apps/webapp/app/components/ListPagination.tsx b/apps/webapp/app/components/ListPagination.tsx index 6e26330677f..32d73d68322 100644 --- a/apps/webapp/app/components/ListPagination.tsx +++ b/apps/webapp/app/components/ListPagination.tsx @@ -23,7 +23,7 @@ export function ListPagination({ list, className }: { list: List; className?: st
@@ -41,7 +41,7 @@ function PreviousButton({ cursor }: { cursor?: string }) { variant={"secondary/small"} LeadingIcon={ChevronLeftIcon} className={cn( - "flex items-center rounded-r-none border-r-0 pl-2 pr-[0.5625rem]", + "flex items-center rounded-r-none border-r-0 pl-2 pr-2.25", !path && "cursor-not-allowed opacity-50" )} onClick={(e) => !path && e.preventDefault()} @@ -63,7 +63,7 @@ function NextButton({ cursor }: { cursor?: string }) { variant={"secondary/small"} TrailingIcon={ChevronRightIcon} className={cn( - "flex items-center rounded-l-none border-l-0 pl-[0.5625rem] pr-2", + "flex items-center rounded-l-none border-l-0 pl-2.25 pr-2", !path && "cursor-not-allowed opacity-50" )} onClick={(e) => !path && e.preventDefault()} diff --git a/apps/webapp/app/components/LoginPageLayout.tsx b/apps/webapp/app/components/LoginPageLayout.tsx index 323faf4ea26..089b324a9c5 100644 --- a/apps/webapp/app/components/LoginPageLayout.tsx +++ b/apps/webapp/app/components/LoginPageLayout.tsx @@ -77,7 +77,7 @@ export function LoginPageLayout({ children }: { children: React.ReactNode }) {
Trusted by developers at -
+
diff --git a/apps/webapp/app/components/ProductHuntBanner.tsx b/apps/webapp/app/components/ProductHuntBanner.tsx index b85893836a1..abb5a146355 100644 --- a/apps/webapp/app/components/ProductHuntBanner.tsx +++ b/apps/webapp/app/components/ProductHuntBanner.tsx @@ -14,7 +14,7 @@ export function ProductHuntBanner() { Vote for us today only! diff --git a/apps/webapp/app/components/UserProfilePhoto.tsx b/apps/webapp/app/components/UserProfilePhoto.tsx index 99febd1c240..4676594dbcc 100644 --- a/apps/webapp/app/components/UserProfilePhoto.tsx +++ b/apps/webapp/app/components/UserProfilePhoto.tsx @@ -26,6 +26,6 @@ export function UserAvatar({ />
) : ( - + ); } diff --git a/apps/webapp/app/components/WarmStarts.tsx b/apps/webapp/app/components/WarmStarts.tsx index 07a894b9e7b..50ffeee7997 100644 --- a/apps/webapp/app/components/WarmStarts.tsx +++ b/apps/webapp/app/components/WarmStarts.tsx @@ -30,7 +30,7 @@ export function WarmStartIconWithTooltip({ }) { return ( } content={} /> @@ -42,14 +42,14 @@ function WarmStartTooltipContent() {
- + A cold start happens when we need to boot up a new machine for your run to execute. This takes longer than a warm start.
- + A warm start happens when we can reuse a machine from a run that recently finished. This takes less time than a cold start. diff --git a/apps/webapp/app/components/admin/FeatureFlagsDialog.tsx b/apps/webapp/app/components/admin/FeatureFlagsDialog.tsx index b1050e60f78..88710370082 100644 --- a/apps/webapp/app/components/admin/FeatureFlagsDialog.tsx +++ b/apps/webapp/app/components/admin/FeatureFlagsDialog.tsx @@ -166,14 +166,14 @@ export function FeatureFlagsDialog({ return (
{key}
-
global: {globalDisplay}
+
global: {globalDisplay}
- +
); } @@ -187,7 +187,7 @@ export function FeatureFlagsDialog({ "flex items-center justify-between rounded-md border px-3 py-2.5", isOverridden ? "border-indigo-500/20 bg-indigo-500/5" - : "border-transparent bg-charcoal-750" + : "border-transparent bg-background-hover" )} >
@@ -199,7 +199,7 @@ export function FeatureFlagsDialog({ > {key}
-
global: {globalDisplay}
+
global: {globalDisplay}
@@ -283,7 +283,7 @@ export function FeatureFlagsDialog({ Preview JSON -
+            
               {jsonPreview}
             
diff --git a/apps/webapp/app/components/admin/backOffice/MaxProjectsSection.tsx b/apps/webapp/app/components/admin/backOffice/MaxProjectsSection.tsx index 8aa4b5c93c9..6b0185e33a1 100644 --- a/apps/webapp/app/components/admin/backOffice/MaxProjectsSection.tsx +++ b/apps/webapp/app/components/admin/backOffice/MaxProjectsSection.tsx @@ -42,7 +42,7 @@ export function MaxProjectsSection({ }, [savedJustNow, hasFieldErrors]); return ( -
+
Maximum projects {!isEditing && ( diff --git a/apps/webapp/app/components/admin/backOffice/RateLimitSection.tsx b/apps/webapp/app/components/admin/backOffice/RateLimitSection.tsx index efbd162dfb0..09d51e69fa3 100644 --- a/apps/webapp/app/components/admin/backOffice/RateLimitSection.tsx +++ b/apps/webapp/app/components/admin/backOffice/RateLimitSection.tsx @@ -94,7 +94,7 @@ export function RateLimitSection({ }; return ( -
+
{title} {!isEditing && ( diff --git a/apps/webapp/app/components/billing/FreePlanUsage.tsx b/apps/webapp/app/components/billing/FreePlanUsage.tsx index 9f51754a11c..274429fb5d0 100644 --- a/apps/webapp/app/components/billing/FreePlanUsage.tsx +++ b/apps/webapp/app/components/billing/FreePlanUsage.tsx @@ -9,7 +9,13 @@ export function FreePlanUsage({ to, percentage }: { to: string; percentage: numb const color = useTransform( widthProgress, [0, 74, 75, 95, 100], - ["#22C55E", "#22C55E", "#F59E0B", "#F43F5E", "#F43F5E"] + [ + "var(--color-success)", + "var(--color-success)", + "var(--color-warning)", + "var(--color-error)", + "var(--color-error)", + ] ); const hasHitLimit = cappedPercentage >= 1; @@ -17,7 +23,7 @@ export function FreePlanUsage({ to, percentage }: { to: string; percentage: numb return (
diff --git a/apps/webapp/app/components/billing/UsageBar.tsx b/apps/webapp/app/components/billing/UsageBar.tsx index e570a029e27..49346492287 100644 --- a/apps/webapp/app/components/billing/UsageBar.tsx +++ b/apps/webapp/app/components/billing/UsageBar.tsx @@ -110,7 +110,7 @@ function Legend({ text, value, position, percentage, tooltipContent }: LegendPro return (
setPrompt(e.target.value)} disabled={isLoading} rows={8} - className="m-0 min-h-10 w-full resize-none border-0 bg-background-bright px-3 py-2.5 text-sm text-text-bright scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600 file:border-0 file:bg-transparent file:text-base file:font-medium placeholder:text-text-dimmed focus:border-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50" + className="m-0 min-h-10 w-full resize-none border-0 bg-background-bright px-3 py-2.5 text-sm text-text-bright scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control file:border-0 file:bg-transparent file:text-base file:font-medium placeholder:text-text-dimmed focus:border-0 focus:outline-hidden focus:ring-0 focus-visible:outline-hidden focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50" onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey && prompt.trim() && !isLoading) { e.preventDefault(); @@ -342,7 +342,7 @@ export function AIQueryInput({ className="overflow-hidden" >
-
+
{isLoading ? ( @@ -390,7 +390,7 @@ export function AIQueryInput({ )}
-
+
{thinking}

}> {thinking}
diff --git a/apps/webapp/app/components/code/ChartConfigPanel.tsx b/apps/webapp/app/components/code/ChartConfigPanel.tsx index 1b61e46a752..1c915c2a449 100644 --- a/apps/webapp/app/components/code/ChartConfigPanel.tsx +++ b/apps/webapp/app/components/code/ChartConfigPanel.tsx @@ -381,7 +381,7 @@ export function ChartConfigPanel({ columns, config, onChange, className }: Chart } updateConfig(updates); }} - className="rounded p-1 text-text-dimmed hover:bg-charcoal-700 hover:text-text-bright" + className="rounded p-1 text-text-dimmed hover:bg-background-raised hover:text-text-bright" title="Remove series" > @@ -406,7 +406,7 @@ export function ChartConfigPanel({ columns, config, onChange, className }: Chart }); } }} - className="flex items-center gap-1 self-start rounded px-1 py-0.5 text-xs text-text-dimmed hover:bg-charcoal-700 hover:text-text-bright" + className="flex items-center gap-1 self-start rounded px-1 py-0.5 text-xs text-text-dimmed hover:bg-background-raised hover:text-text-bright" > Add series @@ -568,7 +568,7 @@ function SeriesColorPicker({
@@ -1110,7 +1110,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({ return (
@@ -1165,7 +1165,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({ return (
@@ -1219,7 +1219,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({ className={cn( "absolute right-0 top-0 h-full w-0.5 cursor-col-resize touch-none select-none", "opacity-0 group-hover/header:opacity-100", - "bg-charcoal-600 hover:bg-indigo-500", + "bg-surface-control hover:bg-indigo-500", header.column.getIsResizing() && "bg-indigo-500 opacity-100" )} /> @@ -1249,7 +1249,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({ height: `${rowVirtualizer.getTotalSize()}px`, position: "relative", }} - className="divide-y divide-charcoal-700 bg-background-bright after:absolute after:bottom-0 after:left-0 after:right-0 after:z-[1] after:h-px after:bg-grid-bright" + className="divide-y divide-grid-bright bg-background-bright after:absolute after:bottom-0 after:left-0 after:right-0 after:z-1 after:h-px after:bg-grid-bright" > {rowVirtualizer.getVirtualItems().map((virtualRow) => { const row = tableRows[virtualRow.index]; @@ -1257,7 +1257,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({ +
{block.title ? ( -
+
{block.title}
) : null} diff --git a/apps/webapp/app/components/dashboard-agent/DashboardAgentComposer.tsx b/apps/webapp/app/components/dashboard-agent/DashboardAgentComposer.tsx index 6f681346175..308a87ebab1 100644 --- a/apps/webapp/app/components/dashboard-agent/DashboardAgentComposer.tsx +++ b/apps/webapp/app/components/dashboard-agent/DashboardAgentComposer.tsx @@ -20,7 +20,7 @@ export function DashboardAgentComposer({ return (
-
+