diff --git a/.changeset/web-confirm-dialog-enter-key.md b/.changeset/web-confirm-dialog-enter-key.md
new file mode 100644
index 000000000..c74cc4e05
--- /dev/null
+++ b/.changeset/web-confirm-dialog-enter-key.md
@@ -0,0 +1,5 @@
+---
+"@moonshot-ai/kimi-code": patch
+---
+
+web: Press Enter to confirm in archive and other confirmation dialogs.
diff --git a/apps/kimi-web/src/components/dialogs/ConfirmDialog.vue b/apps/kimi-web/src/components/dialogs/ConfirmDialog.vue
index 803031631..a4ac057d1 100644
--- a/apps/kimi-web/src/components/dialogs/ConfirmDialog.vue
+++ b/apps/kimi-web/src/components/dialogs/ConfirmDialog.vue
@@ -3,11 +3,19 @@
Dialog (height auto, right-aligned footer). The single confirmation surface
for user actions — driven app-wide by useConfirmDialog(). -->
@@ -39,6 +74,7 @@ function onCancel(): void {
:open="open"
:title="title"
height="auto"
+ :initial-focus="confirmButtonElement"
@update:open="emit('update:open', $event)"
@close="onCancel"
>
@@ -47,7 +83,12 @@ function onCancel(): void {
-
diff --git a/apps/kimi-web/src/components/ui/Dialog.vue b/apps/kimi-web/src/components/ui/Dialog.vue
index 6624a4f92..a11153bce 100644
--- a/apps/kimi-web/src/components/ui/Dialog.vue
+++ b/apps/kimi-web/src/components/ui/Dialog.vue
@@ -22,6 +22,9 @@ const props = withDefaults(defineProps<{
/** When false, the body has no padding so the consumer controls layout
* (e.g. a full-bleed side-nav). */
padded?: boolean;
+ /** Element (or selector / resolver) to receive focus when the dialog opens.
+ * Falls back to the first focusable element, then the dialog panel. */
+ initialFocus?: HTMLElement | string | (() => HTMLElement | null | undefined);
}>(), {
closeOnOverlay: true,
closeOnEsc: true,
@@ -50,6 +53,18 @@ function focusables(): HTMLElement[] {
return panel.value ? Array.from(panel.value.querySelectorAll(FOCUSABLE)) : [];
}
+function resolveInitialFocus(): HTMLElement | null {
+ const { initialFocus } = props;
+ if (!initialFocus) return null;
+ if (typeof initialFocus === 'function') {
+ return initialFocus() ?? null;
+ }
+ if (typeof initialFocus === 'string') {
+ return panel.value?.querySelector(initialFocus) ?? null;
+ }
+ return panel.value?.contains(initialFocus) ? initialFocus : null;
+}
+
function onKeydown(event: KeyboardEvent) {
if (!props.open) return;
if (event.key === 'Escape' && props.closeOnEsc) {
@@ -87,8 +102,9 @@ watch(
openDialogCount.value += 1;
previouslyFocused = document.activeElement;
await nextTick();
+ const initial = resolveInitialFocus();
const list = focusables();
- (list[0] ?? panel.value)?.focus();
+ (initial ?? list[0] ?? panel.value)?.focus();
} else {
openDialogCount.value = Math.max(0, openDialogCount.value - 1);
if (previouslyFocused instanceof HTMLElement) {