diff --git a/studio/frontend/src/app/provider.tsx b/studio/frontend/src/app/provider.tsx
index 8360186d1e..83238dadf0 100644
--- a/studio/frontend/src/app/provider.tsx
+++ b/studio/frontend/src/app/provider.tsx
@@ -291,7 +291,14 @@ export function AppProvider({ children }: AppProviderProps) {
{children}
-
+
);
}
diff --git a/studio/frontend/src/components/ui/sonner.tsx b/studio/frontend/src/components/ui/sonner.tsx
index 80319f10db..5bd3078761 100644
--- a/studio/frontend/src/components/ui/sonner.tsx
+++ b/studio/frontend/src/components/ui/sonner.tsx
@@ -13,11 +13,13 @@ import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";
const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme();
+ // Use resolvedTheme so sonner's data-sonner-theme always matches the class
+ // next-themes puts on ; sonner-side "system" resolution can drift.
+ const { resolvedTheme } = useTheme();
return (
{
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
"--border-radius": "var(--radius)",
+ // Pin close button to the top-right corner inside the toast.
+ // Overrides sonner's default left placement and outside-corner
+ // translate; top offset is set via a rule in index.css since sonner
+ // hardcodes `top: 0` (not a CSS variable).
+ "--toast-close-button-start": "unset",
+ "--toast-close-button-end": "8px",
+ "--toast-close-button-transform": "none",
} as React.CSSProperties
}
// No swipe gestures; keeps toast text selectable.
@@ -71,7 +80,6 @@ const Toaster = ({ ...props }: ToasterProps) => {
classNames: {
toast: "cn-toast",
description: "!text-muted-foreground",
- closeButton: "!top-3 !right-3 !translate-y-0",
},
}}
{...props}
diff --git a/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts b/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts
index c0982a05c0..03f80c19f2 100644
--- a/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts
+++ b/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts
@@ -729,7 +729,6 @@ export function useChatModelRuntime() {
cancelLoading,
),
duration: Infinity,
- closeButton: false,
classNames: MODEL_LOAD_TOAST_CLASSNAMES,
onDismiss: (dismissedToast) => {
if (loadToastIdRef.current !== dismissedToast.id) {
@@ -852,7 +851,6 @@ export function useChatModelRuntime() {
cancelLoading,
),
duration: Infinity,
- closeButton: false,
classNames: MODEL_LOAD_TOAST_CLASSNAMES,
onDismiss: (dismissedToast) => {
if (loadToastIdRef.current !== dismissedToast.id) return;
@@ -892,7 +890,6 @@ export function useChatModelRuntime() {
cancelLoading,
),
duration: Infinity,
- closeButton: false,
classNames: MODEL_LOAD_TOAST_CLASSNAMES,
onDismiss: (dismissedToast) => {
if (loadToastIdRef.current !== dismissedToast.id) return;
@@ -955,7 +952,6 @@ export function useChatModelRuntime() {
cancelLoading,
),
duration: Infinity,
- closeButton: false,
classNames: MODEL_LOAD_TOAST_CLASSNAMES,
onDismiss: (dismissedToast) => {
if (loadToastIdRef.current !== dismissedToast.id) return;
@@ -987,8 +983,7 @@ export function useChatModelRuntime() {
toast.success(`${displayName} loaded`, {
id: toastId,
description: undefined,
- closeButton: false,
- duration: 2000,
+ duration: 8000,
});
}
notifyNative({
@@ -1007,8 +1002,7 @@ export function useChatModelRuntime() {
toast.error(message, {
id: toastId,
description: undefined,
- closeButton: false,
- duration: 5000,
+ duration: 8000,
});
}
notifyNative({
diff --git a/studio/frontend/src/index.css b/studio/frontend/src/index.css
index 237370df1f..3702149059 100644
--- a/studio/frontend/src/index.css
+++ b/studio/frontend/src/index.css
@@ -1160,3 +1160,12 @@
animation: none;
mix-blend-mode: normal;
}
+
+/* Override sonner's hardcoded top: 0 on the toast close button. */
+[data-sonner-toast][data-styled="true"] [data-close-button] {
+ top: 8px !important;
+}
+/* Bump the X stroke so it stays visible against dark backgrounds. */
+[data-sonner-toast][data-styled="true"] [data-close-button] svg {
+ stroke-width: 2.25;
+}