Display
IconCircle
Circular container for icons
Preview
size
icon
weight
Code
import css from "./IconCircle.module.css";
import type { Icon, IconWeight } from "@phosphor-icons/react";
type Size = "xs" | "sm" | "md" | "lg";
interface ResponsiveSize {
base: Size;
mobile?: Size;
tablet?: Size;
desktop?: Size;
wide?: Size;
}
interface IconCircleProps {
size?: Size | ResponsiveSize;
customSize?: string;
customBorder?: string;
weight: IconWeight;
icon: Icon;
}
function getSizeClasses(size: Size | ResponsiveSize): string {
if (typeof size === "string") {
return css[size];
}
const classes = [css[`${size.base}_base`]];
if (size.mobile) classes.push(css[`${size.mobile}_mobile`]);
if (size.tablet) classes.push(css[`${size.tablet}_tablet`]);
if (size.desktop) classes.push(css[`${size.desktop}_desktop`]);
if (size.wide) classes.push(css[`${size.wide}_wide`]);
return classes.join(" ");
}
export default function IconCircle({
size,
customSize,
customBorder,
weight,
icon: IconComponent,
}: IconCircleProps) {
const customStyle =
customSize || customBorder
? { width: customSize, height: customSize, borderWidth: customBorder }
: undefined;
return (
<div
className={`${css.circle} ${size ? getSizeClasses(size) : ""}`}
style={customStyle}
aria-hidden="true"
>
<IconComponent weight={weight} />
</div>
);
}
.circle {
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-full);
border: 2px solid var(--color-text);
color: var(--color-text);
&.xs {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
&.xs_base {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm_base {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md_base {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg_base {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
@media (min-width: 480px) {
&.xs_mobile {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm_mobile {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md_mobile {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg_mobile {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
}
@media (min-width: 768px) {
&.xs_tablet {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm_tablet {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md_tablet {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg_tablet {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
}
@media (min-width: 1024px) {
&.xs_desktop {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm_desktop {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md_desktop {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg_desktop {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
}
@media (min-width: 1440px) {
&.xs_wide {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm_wide {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md_wide {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg_wide {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
}
/* The component owns the icon size, not the consumer. */
svg {
width: 60%;
height: 60%;
}
}
<script setup lang="ts">
import { computed, type Component } from "vue";
type Size = "xs" | "sm" | "md" | "lg";
// The Vue barrel of Phosphor exports no IconWeight type, so it is spelled out here.
type IconWeight = "thin" | "light" | "regular" | "bold" | "fill" | "duotone";
interface ResponsiveSize {
base: Size;
mobile?: Size;
tablet?: Size;
desktop?: Size;
wide?: Size;
}
interface IconCircleProps {
size?: Size | ResponsiveSize;
customSize?: string;
customBorder?: string;
weight: IconWeight;
icon: Component;
}
const { size, customSize, customBorder } = defineProps<IconCircleProps>();
const sizeClasses = computed(() => {
if (!size) return "";
if (typeof size === "string") {
return size;
}
const classes = [`${size.base}_base`];
if (size.mobile) classes.push(`${size.mobile}_mobile`);
if (size.tablet) classes.push(`${size.tablet}_tablet`);
if (size.desktop) classes.push(`${size.desktop}_desktop`);
if (size.wide) classes.push(`${size.wide}_wide`);
return classes.join(" ");
});
const customStyle = computed(() =>
customSize || customBorder
? { width: customSize, height: customSize, borderWidth: customBorder }
: undefined,
);
</script>
<template>
<div
class="circle"
:class="sizeClasses"
:style="customStyle"
aria-hidden="true"
>
<component :is="icon" :weight="weight"></component>
</div>
</template>
<style scoped>
.circle {
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-full);
border: 2px solid var(--color-text);
color: var(--color-text);
&.xs {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
&.xs_base {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm_base {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md_base {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg_base {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
@media (min-width: 480px) {
&.xs_mobile {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm_mobile {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md_mobile {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg_mobile {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
}
@media (min-width: 768px) {
&.xs_tablet {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm_tablet {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md_tablet {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg_tablet {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
}
@media (min-width: 1024px) {
&.xs_desktop {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm_desktop {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md_desktop {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg_desktop {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
}
@media (min-width: 1440px) {
&.xs_wide {
width: var(--size-40);
height: var(--size-40);
border-width: 3px;
}
&.sm_wide {
width: var(--size-56);
height: var(--size-56);
border-width: 4px;
}
&.md_wide {
width: var(--size-80);
height: var(--size-80);
border-width: 6px;
}
&.lg_wide {
width: var(--size-112);
height: var(--size-112);
border-width: 8px;
}
}
/* The component owns the icon size, not the consumer. */
svg {
width: 60%;
height: 60%;
}
}
</style>
Props
| Prop | Type | Required | Description |
|---|---|---|---|
| icon | Icon | required | Phosphor icon component to render. Decorative, hidden from screen readers. |
| weight | IconWeight | required | Icon stroke weight. |
| size | "xs" | "sm" | "md" | "lg" | ResponsiveSize | — | Circle diameter and border width. Omit it only if you pass customSize. Fixed or responsive. |
| customSize | string | — | Custom diameter (any CSS unit); replaces size. |
| customBorder | string | — | Custom border width (any CSS unit). |