Improve website accessibility (#979)

* Improve website accessibility

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Refine homepage search semantics

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Aaron Powell
2026-03-12 14:25:18 +11:00
committed by GitHub
parent eb7d223446
commit 423be2fc70
17 changed files with 293 additions and 160 deletions

View File

@@ -11,7 +11,7 @@ const initialItems = sortAgents(agentsData.items, 'title');
---
<StarlightPage frontmatter={{ title: 'Custom Agents', description: 'Specialized agents that enhance GitHub Copilot for specific technologies, workflows, and domains', template: 'splash', prev: false, next: false, editUrl: false }}>
<main id="main-content">
<div id="main-content">
<PageHeader title="🤖 Custom Agents" description="Specialized agents that enhance GitHub Copilot for specific technologies, workflows, and domains" />
<div class="page-content">
@@ -54,7 +54,7 @@ const initialItems = sortAgents(agentsData.items, 'title');
<ContributeCTA resourceType="agents" />
</div>
</div>
</main>
</div>
<Modal />
<EmbeddedPageData filename="agents.json" data={agentsData} />

View File

@@ -4,7 +4,7 @@ import PageHeader from '../components/PageHeader.astro';
---
<StarlightPage frontmatter={{ title: 'Contributors', description: 'The wonderful people who have contributed to Awesome GitHub Copilot', template: 'splash', prev: false, next: false, editUrl: false }}>
<main id="main-content">
<div id="main-content">
<PageHeader title="🌟 Contributors" description="The wonderful people who have contributed to Awesome GitHub Copilot" />
<div class="page-content">
@@ -281,7 +281,7 @@ import PageHeader from '../components/PageHeader.astro';
</div>
</div>
</div>
</main>
</div>
<style>

View File

@@ -11,7 +11,7 @@ const initialItems = sortHooks(hooksData.items, 'title');
---
<StarlightPage frontmatter={{ title: 'Hooks', description: 'Automated workflows triggered by Copilot coding agent events', template: 'splash', prev: false, next: false, editUrl: false }}>
<main id="main-content">
<div id="main-content">
<PageHeader title="🪝 Hooks" description="Automated workflows triggered by Copilot coding agent events" />
<div class="page-content">
@@ -47,7 +47,7 @@ const initialItems = sortHooks(hooksData.items, 'title');
<ContributeCTA resourceType="hooks" />
</div>
</div>
</main>
</div>
<Modal />

View File

@@ -6,7 +6,7 @@ const base = import.meta.env.BASE_URL;
---
<StarlightPage frontmatter={{ title: 'Awesome GitHub Copilot', template: 'splash', pagefind: false, prev: false, next: false, editUrl: false }} hasSidebar={false}>
<main id="main-content">
<div id="main-content">
<!-- Hero Section -->
<section class="hero" aria-labelledby="hero-heading">
<div class="container">
@@ -14,8 +14,18 @@ const base = import.meta.env.BASE_URL;
<p class="hero-subtitle">Community-contributed agents, instructions, and skills to enhance your GitHub Copilot experience</p>
<div class="hero-search">
<label for="global-search" class="sr-only">Search all resources</label>
<input type="text" id="global-search" placeholder="Search all resources..." autocomplete="off" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="search-results">
<div id="search-results" class="search-results hidden" role="listbox" aria-label="Search results"></div>
<p id="global-search-help" class="sr-only">
Type at least two characters to show matching resources, then press the Down Arrow key to move into the results.
</p>
<p id="global-search-status" class="sr-only" aria-live="polite"></p>
<input
type="text"
id="global-search"
placeholder="Search all resources..."
autocomplete="off"
aria-describedby="global-search-help global-search-status"
>
<div id="search-results" class="search-results hidden" aria-label="Search results"></div>
</div>
</div>
</section>
@@ -92,7 +102,7 @@ const base = import.meta.env.BASE_URL;
</div>
</section>
</main>
</div>
<Modal />

View File

@@ -11,7 +11,7 @@ const initialItems = sortInstructions(instructionsData.items, 'title');
---
<StarlightPage frontmatter={{ title: 'Instructions', description: 'Coding standards and best practices for GitHub Copilot', template: 'splash', prev: false, next: false, editUrl: false }}>
<main id="main-content">
<div id="main-content">
<PageHeader title="📋 Instructions" description="Coding standards and best practices for GitHub Copilot" />
<div class="page-content">
@@ -43,7 +43,7 @@ const initialItems = sortInstructions(instructionsData.items, 'title');
<ContributeCTA resourceType="instructions" />
</div>
</div>
</main>
</div>
<Modal />
<EmbeddedPageData filename="instructions.json" data={instructionsData} />

View File

@@ -11,7 +11,7 @@ const initialItems = pluginsData.items;
---
<StarlightPage frontmatter={{ title: 'Plugins', description: 'Curated plugins of agents, hooks, and skills for specific workflows', template: 'splash', prev: false, next: false, editUrl: false }}>
<main id="main-content">
<div id="main-content">
<PageHeader title="🔌 Plugins" description="Curated plugins of agents, hooks, and skills for specific workflows" />
<div class="page-content">
@@ -45,7 +45,7 @@ const initialItems = pluginsData.items;
<ContributeCTA resourceType="plugins" />
</div>
</div>
</main>
</div>
<Modal />
<EmbeddedPageData filename="plugins.json" data={pluginsData} />

View File

@@ -11,7 +11,7 @@ const initialItems = sortSkills(skillsData.items, 'title');
---
<StarlightPage frontmatter={{ title: 'Skills', description: 'Self-contained agent skills with instructions and bundled resources', template: 'splash', prev: false, next: false, editUrl: false }}>
<main id="main-content">
<div id="main-content">
<PageHeader title="⚡ Skills" description="Self-contained agent skills with instructions and bundled resources" />
<div class="page-content">
@@ -49,7 +49,7 @@ const initialItems = sortSkills(skillsData.items, 'title');
<ContributeCTA resourceType="skills" />
</div>
</div>
</main>
</div>
<Modal />

View File

@@ -13,7 +13,7 @@ const initialItems = toolsData.items.map((item) => ({
---
<StarlightPage frontmatter={{ title: 'Tools', description: 'MCP servers and developer tools for GitHub Copilot', template: 'splash', prev: false, next: false, editUrl: false }}>
<main id="main-content">
<div id="main-content">
<PageHeader title="🔧 Tools" description="MCP servers and developer tools for GitHub Copilot" />
<div class="page-content">
@@ -55,7 +55,7 @@ const initialItems = toolsData.items.map((item) => ({
<ContributeCTA resourceType="tools" />
</div>
</div>
</main>
</div>
<EmbeddedPageData filename="tools.json" data={toolsData} />

View File

@@ -11,7 +11,7 @@ const initialItems = sortWorkflows(workflowsData.items, 'title');
---
<StarlightPage frontmatter={{ title: 'Agentic Workflows', description: 'AI-powered repository automations that run coding agents in GitHub Actions', template: 'splash', prev: false, next: false, editUrl: false }}>
<main id="main-content">
<div id="main-content">
<PageHeader title="⚡ Agentic Workflows" description="">
AI-powered repository automations that run coding agents in <a href="https://gh.io/gh-aw" target="_blank" rel="noopener">GitHub Actions</a>
</PageHeader>
@@ -45,7 +45,7 @@ const initialItems = sortWorkflows(workflowsData.items, 'title');
<ContributeCTA resourceType="workflows" />
</div>
</div>
</main>
</div>
<Modal />
<EmbeddedPageData filename="workflows.json" data={workflowsData} />

View File

@@ -61,44 +61,46 @@ export function renderAgentsHtml(
: escapeHtml(item.title);
return `
<div class="resource-item" data-path="${escapeHtml(item.path)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
${
item.model
? `<span class="resource-tag tag-model">${escapeHtml(
item.model
)}</span>`
: ""
}
${
item.tools
?.slice(0, 3)
.map(
(tool) =>
`<span class="resource-tag">${escapeHtml(tool)}</span>`
)
.join("") || ""
}
${
item.tools && item.tools.length > 3
? `<span class="resource-tag">+${
item.tools.length - 3
} more</span>`
: ""
}
${
item.hasHandoffs
? `<span class="resource-tag tag-handoffs">handoffs</span>`
: ""
}
${getLastUpdatedHtml(item.lastUpdated)}
<article class="resource-item" data-path="${escapeHtml(item.path)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
${
item.model
? `<span class="resource-tag tag-model">${escapeHtml(
item.model
)}</span>`
: ""
}
${
item.tools
?.slice(0, 3)
.map(
(tool) =>
`<span class="resource-tag">${escapeHtml(tool)}</span>`
)
.join("") || ""
}
${
item.tools && item.tools.length > 3
? `<span class="resource-tag">+${
item.tools.length - 3
} more</span>`
: ""
}
${
item.hasHandoffs
? `<span class="resource-tag tag-handoffs">handoffs</span>`
: ""
}
${getLastUpdatedHtml(item.lastUpdated)}
</div>
</div>
</div>
</button>
<div class="resource-actions">
${getInstallDropdownHtml(resourceType, item.path, true)}
${getActionButtonsHtml(item.path, true)}
@@ -108,7 +110,7 @@ export function renderAgentsHtml(
GitHub
</a>
</div>
</div>
</article>
`;
})
.join("");

View File

@@ -59,41 +59,43 @@ export function renderHooksHtml(
: escapeHtml(item.title);
return `
<div class="resource-item" data-path="${escapeHtml(
<article class="resource-item" data-path="${escapeHtml(
item.readmeFile
)}" data-hook-id="${escapeHtml(item.id)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
${item.hooks
.map(
(hook) =>
`<span class="resource-tag tag-hook">${escapeHtml(
hook
)}</span>`
)
.join("")}
${item.tags
.map(
(tag) =>
`<span class="resource-tag tag-tag">${escapeHtml(
tag
)}</span>`
)
.join("")}
${
item.assets.length > 0
? `<span class="resource-tag tag-assets">${
item.assets.length
} asset${item.assets.length === 1 ? "" : "s"}</span>`
: ""
}
${getLastUpdatedHtml(item.lastUpdated)}
)}" data-hook-id="${escapeHtml(item.id)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
${item.hooks
.map(
(hook) =>
`<span class="resource-tag tag-hook">${escapeHtml(
hook
)}</span>`
)
.join("")}
${item.tags
.map(
(tag) =>
`<span class="resource-tag tag-tag">${escapeHtml(
tag
)}</span>`
)
.join("")}
${
item.assets.length > 0
? `<span class="resource-tag tag-assets">${
item.assets.length
} asset${item.assets.length === 1 ? "" : "s"}</span>`
: ""
}
${getLastUpdatedHtml(item.lastUpdated)}
</div>
</div>
</div>
</button>
<div class="resource-actions">
<button class="btn btn-primary download-hook-btn" data-hook-id="${escapeHtml(
item.id
@@ -108,7 +110,7 @@ export function renderHooksHtml(
item.path
)}" class="btn btn-secondary" target="_blank" onclick="event.stopPropagation()" title="View on GitHub">GitHub</a>
</div>
</div>
</article>
`;
})
.join("");

View File

@@ -55,43 +55,120 @@ export async function initHomepage(): Promise<void> {
const resultsDiv = document.getElementById('search-results');
if (searchInput && resultsDiv) {
const statusEl = document.getElementById("global-search-status");
const hideResults = (): void => {
resultsDiv.classList.add("hidden");
};
const showResults = (): void => {
resultsDiv.classList.remove("hidden");
};
const getResultButtons = (): HTMLButtonElement[] =>
Array.from(
resultsDiv.querySelectorAll<HTMLButtonElement>(".search-result")
);
const openResult = (resultEl: HTMLElement): void => {
const path = resultEl.dataset.path;
const type = resultEl.dataset.type;
if (path && type) {
hideResults();
openFileModal(path, type);
}
};
searchInput.addEventListener('input', debounce(() => {
const query = searchInput.value.trim();
if (query.length < 2) {
resultsDiv.classList.add('hidden');
resultsDiv.innerHTML = '';
if (statusEl) {
statusEl.textContent = '';
}
hideResults();
return;
}
const results = search.search(query).slice(0, 10);
if (results.length === 0) {
resultsDiv.innerHTML = '<div class="search-result-empty">No results found</div>';
if (statusEl) {
statusEl.textContent = 'No results found.';
}
} else {
resultsDiv.innerHTML = results.map(item => `
<div class="search-result" data-path="${escapeHtml(item.path)}" data-type="${escapeHtml(item.type)}">
<button type="button" class="search-result" data-path="${escapeHtml(item.path)}" data-type="${escapeHtml(item.type)}">
<span class="search-result-type">${getResourceIcon(item.type)}</span>
<div>
<div class="search-result-title">${search.highlight(item.title, query)}</div>
<div class="search-result-description">${truncate(item.description, 60)}</div>
</div>
</div>
</button>
`).join('');
// Add click handlers
resultsDiv.querySelectorAll('.search-result').forEach(el => {
if (statusEl) {
statusEl.textContent = `${results.length} result${results.length === 1 ? '' : 's'} available.`;
}
getResultButtons().forEach((el, index, buttons) => {
el.addEventListener('click', () => {
const path = (el as HTMLElement).dataset.path;
const type = (el as HTMLElement).dataset.type;
if (path && type) openFileModal(path, type);
openResult(el);
});
el.addEventListener("keydown", (event) => {
switch (event.key) {
case "ArrowDown":
event.preventDefault();
buttons[(index + 1) % buttons.length]?.focus();
break;
case "ArrowUp":
event.preventDefault();
if (index === 0) {
searchInput.focus();
} else {
buttons[index - 1]?.focus();
}
break;
case "Home":
event.preventDefault();
buttons[0]?.focus();
break;
case "End":
event.preventDefault();
buttons[buttons.length - 1]?.focus();
break;
case "Escape":
event.preventDefault();
hideResults();
searchInput.focus();
break;
}
});
});
}
resultsDiv.classList.remove('hidden');
showResults();
}, 200));
searchInput.addEventListener("keydown", (event) => {
if (event.key === "ArrowDown") {
const firstResult = getResultButtons()[0];
if (firstResult) {
event.preventDefault();
firstResult.focus();
}
}
if (event.key === "Escape") {
hideResults();
}
});
// Close results when clicking outside
document.addEventListener('click', (e) => {
if (!searchInput.contains(e.target as Node) && !resultsDiv.contains(e.target as Node)) {
resultsDiv.classList.add('hidden');
hideResults();
}
});
}

View File

@@ -61,17 +61,19 @@ export function renderInstructionsHtml(
: escapeHtml(item.title);
return `
<div class="resource-item" data-path="${escapeHtml(item.path)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${applyToText ? `<span class="resource-tag">applies to: ${escapeHtml(applyToText)}</span>` : ''}
${item.extensions?.slice(0, 4).map((extension) => `<span class="resource-tag tag-extension">${escapeHtml(extension)}</span>`).join('') || ''}
${item.extensions && item.extensions.length > 4 ? `<span class="resource-tag">+${item.extensions.length - 4} more</span>` : ''}
${getLastUpdatedHtml(item.lastUpdated)}
<article class="resource-item" data-path="${escapeHtml(item.path)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${applyToText ? `<span class="resource-tag">applies to: ${escapeHtml(applyToText)}</span>` : ''}
${item.extensions?.slice(0, 4).map((extension) => `<span class="resource-tag tag-extension">${escapeHtml(extension)}</span>`).join('') || ''}
${item.extensions && item.extensions.length > 4 ? `<span class="resource-tag">+${item.extensions.length - 4} more</span>` : ''}
${getLastUpdatedHtml(item.lastUpdated)}
</div>
</div>
</div>
</button>
<div class="resource-actions">
${getInstallDropdownHtml('instructions', item.path, true)}
${getActionButtonsHtml(item.path, true)}
@@ -79,7 +81,7 @@ export function renderInstructionsHtml(
GitHub
</a>
</div>
</div>
</article>
`;
})
.join('');

View File

@@ -70,21 +70,23 @@ export function renderPluginsHtml(
: escapeHtml(item.name);
return `
<div class="resource-item${isExternal ? ' resource-item-external' : ''}" data-path="${escapeHtml(item.path)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${metaTag}
${authorTag}
${item.tags?.slice(0, 4).map((tag) => `<span class="resource-tag">${escapeHtml(tag)}</span>`).join('') || ''}
${item.tags && item.tags.length > 4 ? `<span class="resource-tag">+${item.tags.length - 4} more</span>` : ''}
<article class="resource-item${isExternal ? ' resource-item-external' : ''}" data-path="${escapeHtml(item.path)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${metaTag}
${authorTag}
${item.tags?.slice(0, 4).map((tag) => `<span class="resource-tag">${escapeHtml(tag)}</span>`).join('') || ''}
${item.tags && item.tags.length > 4 ? `<span class="resource-tag">+${item.tags.length - 4} more</span>` : ''}
</div>
</div>
</div>
</button>
<div class="resource-actions">
<a href="${githubHref}" class="btn btn-secondary" target="_blank" rel="noopener noreferrer" onclick="event.stopPropagation()" title="${isExternal ? 'View repository' : 'View on GitHub'}">${isExternal ? 'Repository' : 'GitHub'}</a>
</div>
</div>
</article>
`;
})
.join('');

View File

@@ -65,31 +65,33 @@ export function renderSkillsHtml(
: escapeHtml(item.title);
return `
<div class="resource-item" data-path="${escapeHtml(
<article class="resource-item" data-path="${escapeHtml(
item.skillFile
)}" data-skill-id="${escapeHtml(item.id)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
<span class="resource-tag tag-category">${escapeHtml(
item.category
)}</span>
${
item.hasAssets
? `<span class="resource-tag tag-assets">${
item.assetCount
} asset${item.assetCount === 1 ? "" : "s"}</span>`
: ""
}
<span class="resource-tag">${item.files.length} file${
item.files.length === 1 ? "" : "s"
}</span>
${getLastUpdatedHtml(item.lastUpdated)}
)}" data-skill-id="${escapeHtml(item.id)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(
item.description || "No description"
)}</div>
<div class="resource-meta">
<span class="resource-tag tag-category">${escapeHtml(
item.category
)}</span>
${
item.hasAssets
? `<span class="resource-tag tag-assets">${
item.assetCount
} asset${item.assetCount === 1 ? "" : "s"}</span>`
: ""
}
<span class="resource-tag">${item.files.length} file${
item.files.length === 1 ? "" : "s"
}</span>
${getLastUpdatedHtml(item.lastUpdated)}
</div>
</div>
</div>
</button>
<div class="resource-actions">
<button class="btn btn-primary download-skill-btn" data-skill-id="${escapeHtml(
item.id
@@ -104,7 +106,7 @@ export function renderSkillsHtml(
item.path
)}" class="btn btn-secondary" target="_blank" onclick="event.stopPropagation()" title="View on GitHub">GitHub</a>
</div>
</div>
</article>
`;
})
.join("");

View File

@@ -56,20 +56,22 @@ export function renderWorkflowsHtml(
: escapeHtml(item.title);
return `
<div class="resource-item" data-path="${escapeHtml(item.path)}">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${item.triggers.map((trigger) => `<span class="resource-tag tag-trigger">${escapeHtml(trigger)}</span>`).join('')}
${getLastUpdatedHtml(item.lastUpdated)}
<article class="resource-item" data-path="${escapeHtml(item.path)}" role="listitem">
<button type="button" class="resource-preview">
<div class="resource-info">
<div class="resource-title">${titleHtml}</div>
<div class="resource-description">${escapeHtml(item.description || 'No description')}</div>
<div class="resource-meta">
${item.triggers.map((trigger) => `<span class="resource-tag tag-trigger">${escapeHtml(trigger)}</span>`).join('')}
${getLastUpdatedHtml(item.lastUpdated)}
</div>
</div>
</div>
</button>
<div class="resource-actions">
${getActionButtonsHtml(item.path)}
<a href="${getGitHubUrl(item.path)}" class="btn btn-secondary" target="_blank" onclick="event.stopPropagation()" title="View on GitHub">GitHub</a>
</div>
</div>
</article>
`;
})
.join('');

View File

@@ -275,11 +275,16 @@ body:has(#main-content) {
display: flex;
align-items: flex-start;
gap: 14px;
width: 100%;
padding: 14px 18px;
background: transparent;
border: 0;
cursor: pointer;
border-bottom: 1px solid var(--color-glass-border);
transition: all var(--transition);
text-align: left;
color: inherit;
font: inherit;
}
.search-result:last-child,
@@ -288,10 +293,16 @@ body:has(#main-content) {
}
.search-result:hover,
.search-result:focus-visible,
.search-result-item:hover {
background: var(--color-bg-tertiary);
}
.search-result:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: -2px;
}
.search-result-type {
font-size: 20px;
flex-shrink: 0;
@@ -1464,7 +1475,6 @@ body:has(#main-content) {
justify-content: space-between;
gap: 20px;
transition: all var(--transition);
cursor: pointer;
position: relative;
}
@@ -1481,21 +1491,45 @@ body:has(#main-content) {
transition: opacity var(--transition), left var(--transition);
}
.resource-item:hover {
.resource-item:hover,
.resource-item:focus-within {
background: var(--color-card-hover);
transform: translateX(4px);
box-shadow: var(--shadow);
border-radius: 0px var(--border-radius-lg) var(--border-radius-lg) 0px;
}
.resource-item:hover::before {
.resource-item:hover::before,
.resource-item:focus-within::before {
opacity: 1;
left: -4px;
}
.resource-preview {
flex: 1;
min-width: 0;
display: block;
width: 100%;
padding: 0;
margin: 0;
background: transparent;
border: 0;
color: inherit;
font: inherit;
text-align: left;
cursor: pointer;
}
.resource-preview:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 4px;
border-radius: var(--border-radius);
}
.resource-info {
flex: 1;
min-width: 0;
width: 100%;
}
.resource-title {