diff --git a/app/assets/tailwind/sure-design-system/base.css b/app/assets/tailwind/sure-design-system/base.css
index 991cfc4ef..522cdcf37 100644
--- a/app/assets/tailwind/sure-design-system/base.css
+++ b/app/assets/tailwind/sure-design-system/base.css
@@ -1,6 +1,10 @@
@layer base {
button {
- @apply cursor-pointer focus-visible:outline-gray-900;
+ @apply cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-alpha-black-300;
+
+ @variant theme-dark {
+ @apply focus-visible:ring-alpha-white-300;
+ }
}
hr {
diff --git a/app/components/DS/button.rb b/app/components/DS/button.rb
index a253c04c7..ca5644225 100644
--- a/app/components/DS/button.rb
+++ b/app/components/DS/button.rb
@@ -22,7 +22,6 @@ class DS::Button < DS::Buttonish
def merged_opts
merged_opts = opts.dup || {}
extra_classes = merged_opts.delete(:class)
- href = merged_opts.delete(:href)
data = merged_opts.delete(:data) || {}
if confirm.present?
@@ -33,6 +32,28 @@ class DS::Button < DS::Buttonish
data = data.merge(turbo_frame: frame)
end
+ # `content_tag(:button, ...)` defaults to `type="submit"` per the HTML
+ # spec — meaning a DS::Button rendered inside a form will steal Enter-key
+ # submission from the first text input. Default to `type="button"` so
+ # callers must opt into submit behavior explicitly. `button_to` (href
+ # branch) wraps the button in its own form, so submit there is correct
+ # and we leave its default alone.
+ if href.blank?
+ merged_opts[:type] ||= "button"
+ end
+
+ # Icon-only buttons have no visible text node, so screen readers fall
+ # back to announcing "button" with no name. Derive a humanized fallback
+ # from the icon key so AT users hear *something* meaningful; explicit
+ # `aria: { label: }` on the caller still wins.
+ if icon_only? && icon.present?
+ aria = (merged_opts[:aria] || {}).symbolize_keys
+ if aria[:label].blank? && merged_opts[:"aria-label"].blank?
+ aria[:label] = icon.to_s.tr("-_", " ").capitalize
+ merged_opts[:aria] = aria
+ end
+ end
+
merged_opts.merge(
class: class_names(container_classes, extra_classes),
data: data
diff --git a/app/components/DS/buttonish.rb b/app/components/DS/buttonish.rb
index bd8894be6..f1e864511 100644
--- a/app/components/DS/buttonish.rb
+++ b/app/components/DS/buttonish.rb
@@ -10,7 +10,7 @@ class DS::Buttonish < DesignSystemComponent
},
destructive: {
container_classes: "text-inverse bg-red-500 theme-dark:bg-red-400 hover:bg-red-600 theme-dark:hover:bg-red-500 disabled:bg-red-200 theme-dark:disabled:bg-red-600",
- icon_classes: "fg-white"
+ icon_classes: "text-inverse"
},
outline: {
container_classes: "text-primary border border-secondary bg-transparent hover:bg-surface-hover",
@@ -43,13 +43,13 @@ class DS::Buttonish < DesignSystemComponent
},
md: {
container_classes: "px-3 py-2",
- icon_container_classes: "inline-flex items-center justify-center w-9 h-9",
+ icon_container_classes: "inline-flex items-center justify-center w-11 h-11",
radius_classes: "rounded-lg",
text_classes: "text-sm"
},
lg: {
container_classes: "px-4 py-3",
- icon_container_classes: "inline-flex items-center justify-center w-10 h-10",
+ icon_container_classes: "inline-flex items-center justify-center w-12 h-12",
radius_classes: "rounded-xl",
text_classes: "text-base"
}
diff --git a/app/helpers/styled_form_builder.rb b/app/helpers/styled_form_builder.rb
index ba3b5f81e..277d34aa3 100644
--- a/app/helpers/styled_form_builder.rb
+++ b/app/helpers/styled_form_builder.rb
@@ -89,6 +89,7 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder
@template.render(
DS::Button.new(
text: value,
+ type: "submit",
data: (options[:data] || {}).merge({ turbo_submits_with: "Submitting..." }),
full_width: true
)
diff --git a/app/views/account_sharings/show.html.erb b/app/views/account_sharings/show.html.erb
index b53a6bdff..5700136ba 100644
--- a/app/views/account_sharings/show.html.erb
+++ b/app/views/account_sharings/show.html.erb
@@ -46,7 +46,7 @@
<% end %>
- <%= render DS::Button.new(text: t(".save"), class: "md:w-auto w-full justify-center") %>
+ <%= render DS::Button.new(text: t(".save"), type: :submit, class: "md:w-auto w-full justify-center") %>
<% end %>
<% else %>
@@ -83,7 +83,7 @@
- <%= render DS::Button.new(text: t(".save"), class: "md:w-auto w-full justify-center") %>
+ <%= render DS::Button.new(text: t(".save"), type: :submit, class: "md:w-auto w-full justify-center") %>
<% end %>
diff --git a/app/views/category/deletions/new.html.erb b/app/views/category/deletions/new.html.erb
index cf06b137d..d594dba7a 100644
--- a/app/views/category/deletions/new.html.erb
+++ b/app/views/category/deletions/new.html.erb
@@ -16,6 +16,7 @@
<%= render DS::Button.new(
variant: "destructive",
+ type: :submit,
text: t(".delete_and_leave_uncategorized", category_name: @category.name),
full_width: true,
data: { deletion_target: "destructiveSubmitButton" }
@@ -23,6 +24,7 @@
<%= render DS::Button.new(
text: t(".delete_and_recategorize", category_name: @category.name),
+ type: :submit,
data: { deletion_target: "safeSubmitButton" },
hidden: true,
full_width: true
diff --git a/app/views/family_merchants/merge.html.erb b/app/views/family_merchants/merge.html.erb
index d2fca9c7d..879118d56 100644
--- a/app/views/family_merchants/merge.html.erb
+++ b/app/views/family_merchants/merge.html.erb
@@ -26,6 +26,7 @@
<%= render DS::Button.new(
text: t(".submit"),
+ type: :submit,
full_width: true
) %>
<% end %>
diff --git a/app/views/layouts/shared/_confirm_dialog.html.erb b/app/views/layouts/shared/_confirm_dialog.html.erb
index a25c36f72..cefc5aa13 100644
--- a/app/views/layouts/shared/_confirm_dialog.html.erb
+++ b/app/views/layouts/shared/_confirm_dialog.html.erb
@@ -17,6 +17,7 @@
<%= render DS::Button.new(
text: t(".confirm"),
variant: variant,
+ type: :submit,
autofocus: true,
full_width: true,
value: "confirm",
diff --git a/app/views/onboardings/goals.html.erb b/app/views/onboardings/goals.html.erb
index 646a892d1..8cb36cd99 100644
--- a/app/views/onboardings/goals.html.erb
+++ b/app/views/onboardings/goals.html.erb
@@ -46,6 +46,7 @@
<%= render DS::Button.new(
text: t("onboardings.goals.submit"),
+ type: :submit,
full_width: true
) %>
diff --git a/app/views/properties/address.html.erb b/app/views/properties/address.html.erb
index 489ab62b1..a8b873c7c 100644
--- a/app/views/properties/address.html.erb
+++ b/app/views/properties/address.html.erb
@@ -41,6 +41,7 @@
<%= render DS::Button.new(
text: t(".save"),
variant: "primary",
+ type: :submit,
) %>
<% end %>
diff --git a/app/views/properties/balances.html.erb b/app/views/properties/balances.html.erb
index 3cd96041d..9477213f7 100644
--- a/app/views/properties/balances.html.erb
+++ b/app/views/properties/balances.html.erb
@@ -21,6 +21,7 @@
<%= render DS::Button.new(
text: @account.active? ? t(".save") : t(".next"),
variant: "primary",
+ type: :submit,
) %>
<% end %>
diff --git a/app/views/properties/edit.html.erb b/app/views/properties/edit.html.erb
index 51d674f96..428e60059 100644
--- a/app/views/properties/edit.html.erb
+++ b/app/views/properties/edit.html.erb
@@ -18,6 +18,7 @@
<%= render DS::Button.new(
text: @account.active? ? "Save" : "Next",
variant: "primary",
+ type: :submit,
) %>
<% end %>
diff --git a/app/views/properties/new.html.erb b/app/views/properties/new.html.erb
index 32ab6d960..5e790cce0 100644
--- a/app/views/properties/new.html.erb
+++ b/app/views/properties/new.html.erb
@@ -18,6 +18,7 @@
<%= render DS::Button.new(
text: t(".next"),
variant: "primary",
+ type: :submit,
) %>
<% end %>
diff --git a/app/views/rules/_category_rule_cta.html.erb b/app/views/rules/_category_rule_cta.html.erb
index ff3481f4d..3c7f2ed41 100644
--- a/app/views/rules/_category_rule_cta.html.erb
+++ b/app/views/rules/_category_rule_cta.html.erb
@@ -13,7 +13,7 @@
<%= f.hidden_field :rule_prompt_dismissed_at, value: Time.current %>
<%= tag.div class:"flex gap-2 justify-end" do %>
- <%= render DS::Button.new(text: "Dismiss", variant: "secondary") %>
+ <%= render DS::Button.new(text: "Dismiss", variant: "secondary", type: :submit) %>
<% rule_href = new_rule_path(resource_type: "transaction", action_type: "set_transaction_category", action_value: cta[:category_id], name: cta[:merchant_name]) %>
<%= render DS::Link.new(text: "Create rule", variant: "primary", href: rule_href, frame: :modal) %>
<% end %>
diff --git a/app/views/settings/preferences/show.html.erb b/app/views/settings/preferences/show.html.erb
index 67c04b44a..d2e0fd9a1 100644
--- a/app/views/settings/preferences/show.html.erb
+++ b/app/views/settings/preferences/show.html.erb
@@ -182,7 +182,7 @@
<%= render DS::Button.new(text: t("shared.cancel"), type: :button, variant: :ghost, data: { action: "DS--dialog#close" }) %>
- <%= render DS::Button.new(text: t(".save_currencies")) %>
+ <%= render DS::Button.new(text: t(".save_currencies"), type: :submit) %>
<% end %>
<% end %>
diff --git a/app/views/settings/profiles/show.html.erb b/app/views/settings/profiles/show.html.erb
index dc3e081b9..5c431449d 100644
--- a/app/views/settings/profiles/show.html.erb
+++ b/app/views/settings/profiles/show.html.erb
@@ -19,7 +19,7 @@
- <%= render DS::Button.new(text: t(".save"), class: "md:w-auto w-full justify-center") %>
+ <%= render DS::Button.new(text: t(".save"), type: :submit, class: "md:w-auto w-full justify-center") %>
<% end %>
diff --git a/app/views/subscriptions/upgrade.html.erb b/app/views/subscriptions/upgrade.html.erb
index fd7d49936..2fbf5839a 100644
--- a/app/views/subscriptions/upgrade.html.erb
+++ b/app/views/subscriptions/upgrade.html.erb
@@ -48,6 +48,7 @@
<%= render DS::Button.new(
text: t("subscriptions.upgrade.contribute_and_support_sure"),
variant: "primary",
+ type: :submit,
full_width: true
) %>
diff --git a/app/views/tag/deletions/new.html.erb b/app/views/tag/deletions/new.html.erb
index c069518ec..dcadf6e1d 100644
--- a/app/views/tag/deletions/new.html.erb
+++ b/app/views/tag/deletions/new.html.erb
@@ -16,6 +16,7 @@
<%= render DS::Button.new(
variant: "destructive",
+ type: :submit,
text: t(".delete_and_leave_uncategorized", tag_name: @tag.name),
full_width: true,
data: { deletion_target: "destructiveSubmitButton" }
@@ -23,6 +24,7 @@
<%= render DS::Button.new(
text: t(".delete_and_reassign"),
+ type: :submit,
data: { deletion_target: "safeSubmitButton" },
hidden: true,
full_width: true
diff --git a/app/views/transactions/_attachments.html.erb b/app/views/transactions/_attachments.html.erb
index dba4b464f..b771ca448 100644
--- a/app/views/transactions/_attachments.html.erb
+++ b/app/views/transactions/_attachments.html.erb
@@ -51,6 +51,7 @@
text: t(".upload"),
variant: :primary,
size: :sm,
+ type: :submit,
data: { attachment_upload_target: "submitButton" }
) %>
diff --git a/app/views/transactions/bulk_updates/new.html.erb b/app/views/transactions/bulk_updates/new.html.erb
index 713d38588..47b38bf3f 100644
--- a/app/views/transactions/bulk_updates/new.html.erb
+++ b/app/views/transactions/bulk_updates/new.html.erb
@@ -21,7 +21,7 @@
<%= render DS::Button.new(text: t(".cancel"), variant: "ghost", data: { action: "click->DS--dialog#close" }) %>
- <%= render DS::Button.new(text: t(".save"), data: { bulk_select_scope_param: "bulk_update", action: "bulk-select#submitBulkRequest" }) %>
+ <%= render DS::Button.new(text: t(".save"), type: :submit, data: { bulk_select_scope_param: "bulk_update", action: "bulk-select#submitBulkRequest" }) %>
<% end %>
<% end %>
diff --git a/app/views/transactions/searches/_menu.html.erb b/app/views/transactions/searches/_menu.html.erb
index 0f2e9b09a..db7370e30 100644
--- a/app/views/transactions/searches/_menu.html.erb
+++ b/app/views/transactions/searches/_menu.html.erb
@@ -38,7 +38,7 @@
<%= render DS::Button.new(text: t(".cancel"), type: "button", variant: "ghost", data: { action: "DS--menu#close" }) %>
- <%= render DS::Button.new(text: t(".apply")) %>
+ <%= render DS::Button.new(text: t(".apply"), type: :submit) %>