SocialLinks
Icon links to accounts on X, GitHub, LinkedIn, YouTube, Mastodon, Bluesky and more, each named for screen readers.
Demo
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/social_links.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/social_links.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/social_links.qwe
let social_links_demo_all = [ { kind: "x", href: "https://x.com/quillolang" }, { kind: "github", href: "https://github.com/quillolang" }, { kind: "linkedin", href: "https://linkedin.com/company/quillolang" }, { kind: "youtube", href: "https://youtube.com/@quillolang" }, { kind: "instagram", href: "https://instagram.com/quillolang" }, { kind: "facebook", href: "https://facebook.com/quillolang" }, { kind: "mastodon", href: "https://fosstodon.org/@quillolang" }, { kind: "bluesky", href: "https://bsky.app/profile/quillolang.org" }, { kind: "tiktok", href: "https://tiktok.com/@quillolang" }, { kind: "discord", href: "https://discord.gg/quillolang" }, { kind: "email", href: "mailto:hello@quillolang.org", label: "Email us" }, { kind: "rss", href: "/feed.xml" }, { kind: "website", href: "https://quillolang.org" },]view SocialLinksDemo() = <div.demo-stack> <SocialLinks id="social-links-demo" links={social_links_demo_all}/> <div.demo-row> <SocialLinks variant="outline" links={take(social_links_demo_all, 4)}/> <SocialLinks variant="solid" size="sm" links={take(social_links_demo_all, 4)}/> </div> <SocialLinks variant="outline" size="lg" show_labels={true} links={[social_links_demo_all[1], social_links_demo_all[10], social_links_demo_all[0]]}/> </div>Source
ui/social_links.qwe
// SocialLinks: a row of icon links to accounts elsewhere: X, GitHub, LinkedIn,// YouTube, Instagram, Facebook, Mastodon, Bluesky, TikTok, Discord, email, RSS,// or any website. Each has its network's name for screen readers.//// <SocialLinks links={[// { kind: "github", href: "https://github.com/quillolang" },// { kind: "x", href: "https://x.com/quillolang" },// { kind: "email", href: "mailto:hello@example.com", label: "Email us" },// ]}/>// <SocialLinks variant="outline" size="lg" show_labels={true} links={accounts}/>//// links: a list of { kind, href, label? }: kind is one of "x", "github", "linkedin",// "youtube", "instagram", "facebook", "mastodon", "bluesky", "tiktok", "discord",// "email", "rss" or "website" (a globe, for anything else); label is what screen// readers hear (the network's name by default) and what shows with show_labels.// variant: "ghost" (default), "outline" or "solid". size: "sm", "md" (default) or// "lg". label: the row's name for screen readers ("Social links"). Links to other// sites open in a new tab with rel="me noopener".import ui/theme.qwe use ui_clslet social_links_css = css`.ui-social { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; padding: 0; list-style: none; font: 14px/1 var(--ui-font); }.ui-social a { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-width: var(--s); height: var(--s); padding: 0; border: 1px solid transparent; border-radius: var(--ui-radius-sm); color: var(--ui-muted); text-decoration: none; --s: 36px; transition: color .15s, background .15s, border-color .15s; }.ui-social a:hover { color: var(--ui-accent); background: var(--ui-accent-soft); }.ui-social a:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-social-outline a { border-color: var(--ui-border); color: var(--ui-fg); }.ui-social-outline a:hover { border-color: var(--ui-accent); }.ui-social-solid a { background: var(--ui-subtle); color: var(--ui-fg); }.ui-social-solid a:hover { background: var(--ui-accent); color: var(--ui-accent-fg); }.ui-social svg { flex: none; width: calc(var(--s) * .5); height: calc(var(--s) * .5); }.ui-social-sm a { --s: 30px; }.ui-social-lg a { --s: 44px; }.ui-social-labels a { padding: 0 12px 0 10px; }.ui-social-labels a span { font-weight: 500; }@media (prefers-reduced-motion: reduce) { .ui-social a { transition: none; } }`// The network's name, for screen readers.fn social_links_name(kind: str) -> str = match kind { "x" => "X" "github" => "GitHub" "linkedin" => "LinkedIn" "youtube" => "YouTube" "instagram" => "Instagram" "facebook" => "Facebook" "mastodon" => "Mastodon" "bluesky" => "Bluesky" "tiktok" => "TikTok" "discord" => "Discord" "email" => "Email" "rss" => "RSS feed" _ => "Website" }// The icon, drawn in currentColor on a 24 by 24 grid.fn social_links_icon(kind: str) { let filled = (d: str) => <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d={d}/></svg> let lined = (body) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">{body}</svg> match kind { "x" => filled("M18.2 2h3.4l-7.4 8.5L23 22h-6.8l-5.3-7-6.1 7H1.4l7.9-9.1L1 2h7l4.8 6.4L18.2 2zm-1.2 18h1.9L7.1 3.9H5.1L17 20z") "github" => filled("M12 .5a12 12 0 0 0-3.8 23.4c.6.1.8-.3.8-.6v-2.2c-3.3.7-4-1.4-4-1.4-.5-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-5.9 0-1.3.5-2.4 1.2-3.2-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.7 1.7.3 2.9.1 3.2.8.8 1.2 1.9 1.2 3.2 0 4.6-2.8 5.6-5.5 5.9.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0 0 12 .5z") "linkedin" => filled("M20.4 2H3.6A1.6 1.6 0 0 0 2 3.6v16.8A1.6 1.6 0 0 0 3.6 22h16.8a1.6 1.6 0 0 0 1.6-1.6V3.6A1.6 1.6 0 0 0 20.4 2zM8 19H5V9.5h3V19zM6.5 8.2a1.7 1.7 0 1 1 0-3.4 1.7 1.7 0 0 1 0 3.4zM19 19h-3v-4.6c0-1.1 0-2.5-1.5-2.5s-1.8 1.2-1.8 2.4V19h-3V9.5h2.9v1.3h.1c.4-.8 1.4-1.5 2.8-1.5 3 0 3.5 2 3.5 4.5V19z") "youtube" => filled("M23 7.2a2.9 2.9 0 0 0-2-2C19.2 4.7 12 4.7 12 4.7s-7.2 0-9 .5a2.9 2.9 0 0 0-2 2A30 30 0 0 0 .5 12a30 30 0 0 0 .5 4.8 2.9 2.9 0 0 0 2 2c1.8.5 9 .5 9 .5s7.2 0 9-.5a2.9 2.9 0 0 0 2-2 30 30 0 0 0 .5-4.8 30 30 0 0 0-.5-4.8zM9.7 15.1V8.9l6 3.1-6 3.1z") "instagram" => lined([<rect x="2.5" y="2.5" width="19" height="19" rx="5"/>, <circle cx="12" cy="12" r="4.2"/>, <circle cx="17.6" cy="6.4" r=".9" fill="currentColor"/>]) "facebook" => filled("M24 12a12 12 0 1 0-13.9 11.9v-8.4h-3V12h3V9.4c0-3 1.8-4.7 4.5-4.7 1.3 0 2.7.2 2.7.2v3h-1.5c-1.5 0-2 .9-2 1.9V12h3.3l-.5 3.5h-2.8v8.4A12 12 0 0 0 24 12z") "mastodon" => filled("M21.3 13.6c-.3 1.5-2.6 3.1-5.2 3.4-1.4.2-2.7.3-4.1.2-2.3-.1-4.1-.5-4.1-.5v.6c.3 2.3 2.3 2.4 4.2 2.5 1.9.1 3.6-.5 3.6-.5l.1 1.7s-1.3.7-3.7.9c-1.3.1-2.9-.1-4.8-.5C3.2 20.4 2.5 16 2.4 11.6V8.1c0-4.5 3-5.8 3-5.8C6.9 1.6 9.4 1.3 12 1.3h.1c2.6 0 5.1.3 6.6 1 0 0 3 1.3 3 5.8 0 0 0 3.3-.4 5.5zm-3.1-5.2v5.5h-2.2V8.6c0-1.1-.5-1.7-1.4-1.7-1 0-1.6.7-1.6 2v2.9h-2.2V8.9c0-1.3-.5-2-1.6-2-1 0-1.4.6-1.4 1.7v5.3H5.6V8.4c0-1.1.3-2 .9-2.6.6-.7 1.4-1 2.4-1 1.1 0 2 .4 2.6 1.3l.5.9.6-.9c.6-.9 1.5-1.3 2.6-1.3 1 0 1.8.3 2.4 1 .6.6.9 1.5.9 2.6z") "bluesky" => filled("M12 10.8c-1.1-2.1-4-6-6.8-8C2.6 1 1.6 1.3.9 1.6.2 2 0 3.1 0 3.8c0 .7.4 5.6.6 6.4.8 2.7 3.7 3.6 6.4 3.3-3.9.6-7.4 2-2.8 7 5 5.2 6.9-1.1 7.8-4.3.9 3.2 2 9.3 7.7 4.3 4.3-4.3 1.2-6.4-2.7-7 2.7.3 5.6-.6 6.4-3.3.2-.8.6-5.7.6-6.4 0-.7-.2-1.8-.9-2.2-.7-.3-1.7-.6-4.3 1.2-2.8 2-5.7 5.9-6.8 8z") "tiktok" => filled("M19.6 6.7a4.8 4.8 0 0 1-3.8-4.2V2h-3.4v13.7a2.9 2.9 0 1 1-2-2.8V9.4a6.3 6.3 0 1 0 5.4 6.3V8.8a8.2 8.2 0 0 0 4.8 1.5V6.9l-1-.2z") "discord" => filled("M20.3 4.4A19.8 19.8 0 0 0 15.4 3l-.6 1.3a18 18 0 0 0-5.6 0L8.6 3a19.7 19.7 0 0 0-4.9 1.5C.6 9.1-.2 13.7.2 18.2a19.9 19.9 0 0 0 6 3l1.3-2a12.9 12.9 0 0 1-2-1l.5-.4a14.2 14.2 0 0 0 12 0l.5.4-2 1 1.3 2a19.8 19.8 0 0 0 6-3c.5-5.2-.8-9.8-3.5-13.8zM8 15.4c-1.2 0-2.1-1.1-2.1-2.4S6.8 10.6 8 10.6s2.2 1.1 2.1 2.4c0 1.3-.9 2.4-2.1 2.4zm8 0c-1.2 0-2.1-1.1-2.1-2.4s.9-2.4 2.1-2.4 2.2 1.1 2.1 2.4c0 1.3-.9 2.4-2.1 2.4z") "email" => lined([<rect x="2.5" y="5" width="19" height="14" rx="2.5"/>, <path d="m3 7 9 6 9-6"/>]) "rss" => lined([<path d="M4 11a9 9 0 0 1 9 9M4 4a16 16 0 0 1 16 16"/>, <circle cx="5" cy="19" r="1.2" fill="currentColor"/>]) _ => lined([<circle cx="12" cy="12" r="9.5"/>, <path d="M2.5 12h19M12 2.5c2.8 3 2.8 16 0 19M12 2.5c-2.8 3-2.8 16 0 19"/>]) }}view SocialLinks(links: list, variant: str = "ghost", size: str = "md", show_labels: bool = false, label: str = "Social links", id: str? = null, class: str? = null) { style global social_links_css <ul id={id} class={ui_cls(["ui-social", variant != "ghost" && `ui-social-${variant}`, size != "md" && `ui-social-${size}`, show_labels && "ui-social-labels", class])} aria-label={label}> {for l in links => { let name = l.label ?? social_links_name(l.kind ?? "website") let away = starts_with(l.href, "http") <li><a href={l.href} aria-label={show_labels ? null : name} title={show_labels ? null : name} target={away ? "_blank" : null} rel={away ? "me noopener" : null}> {social_links_icon(l.kind ?? "website")} {show_labels && <span>{name}</span>} </a></li> }} </ul>}Related
TestimonialsQuotes from people with their picture, role and company, in a row that scrolls or a grid that wraps.9 views · 7 downloadsFooterNavColumns of grouped links for a site's footer, each under its title, wrapping on narrow screens.5 views · 7 downloadsMegaMenuA top bar whose items open wide panels of grouped links under the whole bar, on hover and from the keyboard, with no script.5 views · 7 downloadsArticleCardA post to click into: image, category, title, excerpt, author, date and reading time; image on top or to the side.5 views · 7 downloadsNewsListHeadlines in a compact list with source, time and a small picture; numbered for a most-read list.8 views · 7 downloadsTestimonialCardA customer's quote with their picture, name, title and company or logo, and stars if they gave some.5 views · 6 downloads
Thread
Thread 0 posts
On SocialLinks: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.