diff --git a/app/controllers/admin/forms_controller.rb b/app/controllers/admin/forms_controller.rb
index bad184bab..7656b4725 100644
--- a/app/controllers/admin/forms_controller.rb
+++ b/app/controllers/admin/forms_controller.rb
@@ -518,6 +518,7 @@ def form_params
:notification_frequency,
:logo,
:header_logo_display,
+ :logo_alt_text,
:modal_button_text,
:success_text_heading,
:success_text,
@@ -561,7 +562,7 @@ def form_params
def form_logo_params
params.require(:form).permit(
- :logo, :header_logo_display
+ :logo, :header_logo_display, :logo_alt_text
)
end
diff --git a/app/models/form.rb b/app/models/form.rb
index 7527c7cdd..c998f7bcb 100644
--- a/app/models/form.rb
+++ b/app/models/form.rb
@@ -42,6 +42,12 @@ class Form < ApplicationRecord
enum :header_logo_display, { banner: 'banner', square: 'square' }, default: 'banner', prefix: true
+ def logo_alt_text_or_default
+ return logo_alt_text if logo_alt_text.present?
+
+ "#{organization&.name} logo"
+ end
+
def self.my_forms(user, aasm_state)
if user.organizational_form_approver?
items = user.organization.forms
@@ -340,6 +346,7 @@ def touchpoints_js_string
'form-header-logo-square'
end
end,
+ logo_alt_text: (logo_alt_text_or_default if logo.present?),
questions: ordered_questions.map do |q|
{
answer_field: q.answer_field,
diff --git a/app/serializers/form_serializer.rb b/app/serializers/form_serializer.rb
index db3f46790..de5264053 100644
--- a/app/serializers/form_serializer.rb
+++ b/app/serializers/form_serializer.rb
@@ -23,6 +23,7 @@ class FormSerializer < ActiveModel::Serializer
:whitelist_url_9,
:whitelist_test_url,
:header_logo_display,
+ :logo_alt_text,
:success_text_heading,
:success_text,
:modal_button_text,
diff --git a/app/serializers/full_form_serializer.rb b/app/serializers/full_form_serializer.rb
index d90a1bd84..32e5c8cd6 100644
--- a/app/serializers/full_form_serializer.rb
+++ b/app/serializers/full_form_serializer.rb
@@ -45,6 +45,7 @@ def links
:whitelist_url_9,
:whitelist_test_url,
:header_logo_display,
+ :logo_alt_text,
:success_text_heading,
:success_text,
:modal_button_text,
diff --git a/app/views/admin/forms/_logo_display.html.erb b/app/views/admin/forms/_logo_display.html.erb
index 9e478032b..cc87788db 100644
--- a/app/views/admin/forms/_logo_display.html.erb
+++ b/app/views/admin/forms/_logo_display.html.erb
@@ -22,9 +22,9 @@
<% end %>
@@ -44,6 +44,17 @@
<%= f.label :header_logo_display, "Display as square (80px wide by 80px tall)", class: "usa-radio__label", value: "square" %>
+
+ <%= f.label :logo_alt_text, "Logo alt text", class: "usa-label text-uppercase font-body-3xs" %>
+
+ Describe the logo for people using screen readers.
+ Leave blank to use "<%= form.organization&.name %> logo".
+
+ <%= f.text_field :logo_alt_text,
+ value: form.logo_alt_text,
+ class: "usa-input",
+ aria_describedby: "logo-alt-text-hint" %>
+
<%= f.submit "Update Logo Display", class: "usa-button usa-button-outline" %>
<%- if form.logo.present? %>
diff --git a/app/views/components/forms/_logo_and_title.html.erb b/app/views/components/forms/_logo_and_title.html.erb
index 7881a8037..baf6a58eb 100644
--- a/app/views/components/forms/_logo_and_title.html.erb
+++ b/app/views/components/forms/_logo_and_title.html.erb
@@ -6,11 +6,11 @@
<%- if form.header_logo_display_banner? %>
<%= image_tag(form.logo.tag.url,
- alt: "#{form.organization.name} banner",
+ alt: form.logo_alt_text_or_default,
class: "form-header-logo") %>
<% elsif form.header_logo_display_square? %>
<%= image_tag(form.logo.logo_square.url,
- alt: "#{form.organization.name} logo",
+ alt: form.logo_alt_text_or_default,
class: "form-header-logo-square") %>
<% end %>
diff --git a/db/migrate/20260618210000_add_logo_alt_text_to_forms.rb b/db/migrate/20260618210000_add_logo_alt_text_to_forms.rb
new file mode 100644
index 000000000..91b45ed87
--- /dev/null
+++ b/db/migrate/20260618210000_add_logo_alt_text_to_forms.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddLogoAltTextToForms < ActiveRecord::Migration[8.1]
+ def change
+ add_column :forms, :logo_alt_text, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b004de4b9..70a786c32 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[8.1].define(version: 2026_06_01_125209) do
+ActiveRecord::Schema[8.1].define(version: 2026_06_18_210000) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@@ -267,6 +267,7 @@
t.string "legacy_touchpoint_uuid"
t.boolean "load_css", default: true
t.string "logo"
+ t.string "logo_alt_text"
t.string "medium"
t.string "modal_button_text"
t.string "name"
diff --git a/db/seeds.rb b/db/seeds.rb
index 5ad0b0630..730e83b31 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -85,7 +85,7 @@ def production_suitable_seeds
org_2 = Organization.create!({
name: 'Farmers.gov',
- domain: 'example.gov',
+ domain: 'farmers.gov',
url: 'https://farmers.gov',
abbreviation: 'FARMERS'
})
diff --git a/spec/controllers/admin/forms_controller_spec.rb b/spec/controllers/admin/forms_controller_spec.rb
index 743f2f48f..bf5f890c8 100644
--- a/spec/controllers/admin/forms_controller_spec.rb
+++ b/spec/controllers/admin/forms_controller_spec.rb
@@ -309,6 +309,18 @@
expect(form.header_logo_display).to eq('banner')
expect(response).to render_template(:update_display_logo)
end
+
+ it 'updates the logo alt text' do
+ patch :update_display_logo, params: {
+ id: form.to_param,
+ form: { logo: logo_file, header_logo_display: 'banner', logo_alt_text: 'Agency seal' },
+ format: :js
+ }, session: valid_session
+
+ form.reload
+ expect(form.logo_alt_text).to eq('Agency seal')
+ expect(response).to render_template(:update_display_logo)
+ end
end
context 'with invalid file type' do
diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb
index eae1e7291..15287c74c 100644
--- a/spec/models/form_spec.rb
+++ b/spec/models/form_spec.rb
@@ -114,6 +114,22 @@
end
end
+ describe '#logo_alt_text_or_default' do
+ context 'when logo_alt_text is set' do
+ it 'returns the configured alt text' do
+ form.update(logo_alt_text: 'Agency seal')
+ expect(form.logo_alt_text_or_default).to eq('Agency seal')
+ end
+ end
+
+ context 'when logo_alt_text is blank' do
+ it 'falls back to the organization name' do
+ form.update(logo_alt_text: nil)
+ expect(form.logo_alt_text_or_default).to eq("#{organization.name} logo")
+ end
+ end
+ end
+
describe '#user_role?' do
context 'without user_role' do
it 'returns nil' do