Compare commits
2 Commits
step35/595
...
step35/546
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1319dd7ba | ||
|
|
5cb71b666c |
@@ -6,6 +6,8 @@ Applied fixes identified by the accessibility audit (#492):
|
||||
|
||||
| Fix | Issue | WCAG | Description |
|
||||
|-----|-------|------|-------------|
|
||||
| V1 | #551 | 2.4.1 | Skip navigation link (not template-based — frontend) |
|
||||
| V2 | #546 | 3.3.2 | `aria-label` on Explore/Repositories filter & search inputs (25 inputs) |
|
||||
| R1 | #551 | Best Practice | Password visibility toggle (eye icon) on sign-in page |
|
||||
| R2 | #552 | 3.3.1 | `aria-required="true"` on required form fields |
|
||||
| R3 | #553 | 4.1.2 | `aria-label` on star/fork count links ("2 stars", "0 forks") |
|
||||
@@ -19,15 +21,23 @@ deploy/gitea-a11y/
|
||||
├── README.md
|
||||
└── custom/
|
||||
├── public/
|
||||
│ ├── css/
|
||||
│ └── js/
|
||||
│ └── css/
|
||||
│ └── a11y-fixes.css
|
||||
└── templates/
|
||||
├── custom/
|
||||
│ └── time_relative.tmpl # R4: <time> helper
|
||||
│ ├── a11y_head.tmpl # R2 a11y CSS include hook
|
||||
│ ├── header_banner.tmpl # R5 banner landmark
|
||||
│ └── time_relative.tmpl # R4 <time> wrapper
|
||||
├── repo/
|
||||
│ └── list_a11y.tmpl # R3: aria-label on counts
|
||||
└── user/auth/
|
||||
└── signin_inner.tmpl # R1+R2: password toggle + aria-required
|
||||
│ └── list_a11y.tmpl # R3 star/fork aria-label partial
|
||||
├── shared/
|
||||
│ ├── repo/
|
||||
│ │ └── search.tmpl # V2: aria-label on filter/sort radio inputs
|
||||
│ └── search/
|
||||
│ └── input.tmpl # V2: aria-label on search input
|
||||
└── user/
|
||||
└── auth/
|
||||
└── signin_inner.tmpl # R1+R2: password toggle + aria-required
|
||||
```
|
||||
|
||||
## Deploy
|
||||
@@ -38,27 +48,48 @@ bash deploy/gitea-a11y/deploy-gitea-a11y.sh
|
||||
bash deploy/gitea-a11y/deploy-gitea-a11y.sh root@my-gitea-host.com
|
||||
```
|
||||
|
||||
## Template Overrides
|
||||
## Fix Details
|
||||
|
||||
Gitea supports custom template overrides by placing files in `custom/templates/`.
|
||||
The templates here override the default Gitea templates with a11y improvements.
|
||||
### V2: 25 Form Inputs Without Labels
|
||||
|
||||
### R1: Password Visibility Toggle
|
||||
**Page:** Explore/Repositories (`/explore/repos`)
|
||||
**Criterion:** WCAG 3.3.2 — Labels or Instructions
|
||||
**Severity:** High
|
||||
**Audit:** #492, Issue #546
|
||||
|
||||
Adds an eye icon (👁) button next to the password field that toggles between
|
||||
`type="password"` and `type="text"`. Updates `aria-label` dynamically.
|
||||
The search field and all radio button inputs in the filter and sort dropdowns lacked programmatic label associations. This made the controls inaccessible to screen reader users.
|
||||
|
||||
### R2: aria-required
|
||||
**Changes:**
|
||||
|
||||
Adds `aria-required="true"` to the username and password inputs, which
|
||||
properly communicates required state to screen readers.
|
||||
| Element | Input Name | Fix |
|
||||
|---------|-----------|-----|
|
||||
| Search field | `q` | `aria-label` on input (via `shared/search/input.tmpl`) |
|
||||
| Clear filter | `clear-filter` | `aria-label="Clear all filters"` |
|
||||
| Archived | `archived` (value 1) | `aria-label="Show archived repositories"` |
|
||||
| | `archived` (value 0) | `aria-label="Exclude archived repositories"` |
|
||||
| Fork | `fork` (value 1) | `aria-label="Show only forked repositories"` |
|
||||
| | `fork` (value 0) | `aria-label="Exclude forked repositories"` |
|
||||
| Mirror | `mirror` (value 1) | `aria-label="Show only mirrored repositories"` |
|
||||
| | `mirror` (value 0) | `aria-label="Exclude mirrored repositories"` |
|
||||
| Template | `template` (value 1) | `aria-label="Show only repository templates"` |
|
||||
| | `template` (value 0) | `aria-label="Exclude repository templates"` |
|
||||
| Private | `private` (value 1) | `aria-label="Show only private repositories"` |
|
||||
| | `private` (value 0) | `aria-label="Show only public repositories"` |
|
||||
| Sort | `sort` — newest | `aria-label="Sort by newest"` |
|
||||
| | sort — oldest | `aria-label="Sort by oldest"` |
|
||||
| | sort — alphabetically | `aria-label="Sort alphabetically"` |
|
||||
| | sort — reversealphabetically | `aria-label="Sort in reverse alphabetical order"` |
|
||||
| | sort — recentupdate | `aria-label="Sort by most recent update"` |
|
||||
| | sort — leastupdate | `aria-label="Sort by least recent update"` |
|
||||
| | sort — moststars | `aria-label="Sort by most stars"` |
|
||||
| | sort — feweststars | `aria-label="Sort by fewest stars"` |
|
||||
| | sort — mostforks | `aria-label="Sort by most forks"` |
|
||||
| | sort — fewestforks | `aria-label="Sort by fewest forks"` |
|
||||
| | sort — size | `aria-label="Sort by smallest size"` |
|
||||
| | sort — reversesize | `aria-label="Sort by largest size"` |
|
||||
|
||||
### R3: Star/Fork aria-label
|
||||
**Files Modified:**
|
||||
- `deploy/gitea-a11y/custom/templates/shared/search/input.tmpl` (new)
|
||||
- `deploy/gitea-a11y/custom/templates/shared/repo/search.tmpl` (new)
|
||||
|
||||
Wraps star and fork count links with `aria-label="2 stars"` so screen
|
||||
readers announce the meaning, not just the number.
|
||||
|
||||
### R4: `<time>` Elements
|
||||
|
||||
Wraps relative timestamps ("2 minutes ago") in `<time datetime="2026-04-13T17:00:00Z">`
|
||||
providing both human-readable text and machine-readable ISO 8601 dates.
|
||||
**Total inputs fixed:** 25
|
||||
|
||||
64
deploy/gitea-a11y/custom/templates/shared/repo/search.tmpl
Normal file
64
deploy/gitea-a11y/custom/templates/shared/repo/search.tmpl
Normal file
@@ -0,0 +1,64 @@
|
||||
<div class="ui small secondary filter menu">
|
||||
<form id="repo-search-form" class="ui form ignore-dirty tw-flex-1 tw-flex tw-items-center tw-gap-x-2">
|
||||
{{if .Language}}<input type="hidden" name="language" value="{{.Language}}">{{end}}
|
||||
{{if .PageIsExploreRepositories}}<input type="hidden" name="only_show_relevant" value="{{.OnlyShowRelevant}}">{{end}}
|
||||
{{if .TabName}}<input type="hidden" name="tab" value="{{.TabName}}">{{end}}
|
||||
{{if .TopicOnly}}<input type="hidden" name="topic" value="{{.TopicOnly}}">{{end}}
|
||||
<div class="ui small fluid action input tw-flex-1">
|
||||
{{template "shared/search/input" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.repo_kind")}}
|
||||
{{template "shared/search/button"}}
|
||||
</div>
|
||||
<!-- Filter -->
|
||||
<div class="item ui small dropdown jump">
|
||||
<span class="text">{{ctx.Locale.Tr "filter"}}</span>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu flex-items-menu">
|
||||
<label class="item"><input type="radio" name="clear-filter" aria-label="{{ctx.Locale.Tr "filter.clear"}}"> {{ctx.Locale.Tr "filter.clear"}}</label>
|
||||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="archived" {{if .IsArchived.Value}}checked{{end}} value="1" aria-label="{{ctx.Locale.Tr "filter.is_archived"}}"> {{ctx.Locale.Tr "filter.is_archived"}}</label>
|
||||
<label class="item"><input type="radio" name="archived" {{if (not (.IsArchived.ValueOrDefault true))}}checked{{end}} value="0" aria-label="{{ctx.Locale.Tr "filter.not_archived"}}"> {{ctx.Locale.Tr "filter.not_archived"}}</label>
|
||||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="fork" {{if .IsFork.Value}}checked{{end}} value="1" aria-label="{{ctx.Locale.Tr "filter.is_fork"}}"> {{ctx.Locale.Tr "filter.is_fork"}}</label>
|
||||
<label class="item"><input type="radio" name="fork" {{if (not (.IsFork.ValueOrDefault true))}}checked{{end}} value="0" aria-label="{{ctx.Locale.Tr "filter.not_fork"}}"> {{ctx.Locale.Tr "filter.not_fork"}}</label>
|
||||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="mirror" {{if .IsMirror.Value}}checked{{end}} value="1" aria-label="{{ctx.Locale.Tr "filter.is_mirror"}}"> {{ctx.Locale.Tr "filter.is_mirror"}}</label>
|
||||
<label class="item"><input type="radio" name="mirror" {{if (not (.IsMirror.ValueOrDefault true))}}checked{{end}} value="0" aria-label="{{ctx.Locale.Tr "filter.not_mirror"}}"> {{ctx.Locale.Tr "filter.not_mirror"}}</label>
|
||||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="template" {{if .IsTemplate.Value}}checked{{end}} value="1" aria-label="{{ctx.Locale.Tr "filter.is_template"}}"> {{ctx.Locale.Tr "filter.is_template"}}</label>
|
||||
<label class="item"><input type="radio" name="template" {{if (not (.IsTemplate.ValueOrDefault true))}}checked{{end}} value="0" aria-label="{{ctx.Locale.Tr "filter.not_template"}}"> {{ctx.Locale.Tr "filter.not_template"}}</label>
|
||||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="private" {{if .IsPrivate.Value}}checked{{end}} value="1" aria-label="{{ctx.Locale.Tr "filter.private"}}"> {{ctx.Locale.Tr "filter.private"}}</label>
|
||||
<label class="item"><input type="radio" name="private" {{if (not (.IsPrivate.ValueOrDefault true))}}checked{{end}} value="0" aria-label="{{ctx.Locale.Tr "filter.public"}}"> {{ctx.Locale.Tr "filter.public"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sort -->
|
||||
<div class="item ui small dropdown jump">
|
||||
<span class="text">{{ctx.Locale.Tr "repo.issues.filter_sort"}}</span>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu">
|
||||
<label class="{{if eq .SortType "newest"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "newest"}}checked{{end}} value="newest" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</label>
|
||||
<label class="{{if eq .SortType "oldest"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "oldest"}}checked{{end}} value="oldest" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</label>
|
||||
<label class="{{if eq .SortType "alphabetically"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "alphabetically"}}checked{{end}} value="alphabetically" aria-label="{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}"> {{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</label>
|
||||
<label class="{{if eq .SortType "reversealphabetically"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "reversealphabetically"}}checked{{end}} value="reversealphabetically" aria-label="{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}"> {{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</label>
|
||||
<label class="{{if eq .SortType "recentupdate"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "recentupdate"}}checked{{end}} value="recentupdate" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}</label>
|
||||
<label class="{{if eq .SortType "leastupdate"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "leastupdate"}}checked{{end}} value="leastupdate" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}</label>
|
||||
{{if not .DisableStars}}
|
||||
<label class="{{if eq .SortType "moststars"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "moststars"}}checked{{end}} value="moststars" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.moststars"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.moststars"}}</label>
|
||||
<label class="{{if eq .SortType "feweststars"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "feweststars"}}checked{{end}} value="feweststars" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.feweststars"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.feweststars"}}</label>
|
||||
{{end}}
|
||||
<label class="{{if eq .SortType "mostforks"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "mostforks"}}checked{{end}} value="mostforks" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.mostforks"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.mostforks"}}</label>
|
||||
<label class="{{if eq .SortType "fewestforks"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "fewestforks"}}checked{{end}} value="fewestforks" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.fewestforks"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.fewestforks"}}</label>
|
||||
<label class="{{if eq .SortType "size"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "size"}}checked{{end}} value="size" aria-label="{{ctx.Locale.Tr "repo.issues.label.filter_sort.by_size"}}"> {{ctx.Locale.Tr "repo.issues.label.filter_sort.by_size"}}</label>
|
||||
<label class="{{if eq .SortType "reversesize"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "reversesize"}}checked{{end}} value="reversesize" aria-label="{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_by_size"}}"> {{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_by_size"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{if and .PageIsExploreRepositories .OnlyShowRelevant}}
|
||||
<div class="ui message">
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "explore.relevant_repositories_tooltip"}}">
|
||||
{{ctx.Locale.Tr "explore.relevant_repositories" (printf "?only_show_relevant=0&sort=%s&q=%s&language=%s" $.SortType (QueryEscape $.Keyword) (QueryEscape $.Language))}}
|
||||
</span>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="divider"></div>
|
||||
@@ -0,0 +1,2 @@
|
||||
{{/* a11y V2 fix: add aria-label for search on explore page */}}
|
||||
<input type="search" name="q"{{with .Value}} value="{{.}}"{{end}} maxlength="255" spellcheck="false" placeholder="{{with .Placeholder}}{{.}}{{else}}{{ctx.Locale.Tr "search.search"}}{{end}}"{{if .Disabled}} disabled{{end}}{{if .PageIsExploreRepositories}} aria-label="{{ctx.Locale.Tr "search.repo_kind"}}"{{end}}>
|
||||
Reference in New Issue
Block a user