This is a React library that allows you to control inline SVG (SVG in HTML) from Sass.
This library was inspired by a great library called react-inlinesvg.
It has almost the same functionality as react-inlinesvg, but with more convenience and flexibility.
View the demo
- 🏖 Easy to use:Just use the mixins provided
- 🛠 Flexible: Various controls are available from Sass
- 🚀 Performance: Faster speeds with a more optimized cache mechanism
- 📌 SSR: Avoid layout deviations due to initial display elements
- 🟦 Typescript: Nicely typed
Function | react-sass-inlinesvg | react-inlinesvg | smart-svg |
---|---|---|---|
Specify SVG in Sass | ✅ | ❌ | ✅ |
Specify SVG in JSX | ✅ | ✅ | ❌ |
Style control for individual child elements in SVG | ✅ | ✅ | ❌ |
SVG coloring | ✅ | ✅ | ✅ |
Circular and rectangular supports | ✅ | ❌ | ✅ |
SVG display for pseudo-elements | ❌ | ❌ | ✅ |
Use outside of React | ❌ | ❌ | ✅ |
IE11 Support | ✅ | ✅ | ❌ |
performance | A | C | A+ |
Articles on implementation innovations and performance details.
https://dwango.github.io/articles/2022-12_nicolive_svg/
The following will help you in selecting a library.
- react-sass-inlinesvg - This is useful when you want to apply different styles to individual child elements within an SVG element and want to specify which SVG to display from the Sass.
- react-inlinesvg - It is a stable library.
- smart-svg - This is the smartest way if it meets the functional requirements.
yarn add react-sass-inlinesvg
npm i react-sass-inlinesvg
yarn start
npm run start
To use react-sass-inlinesvg with the component name SVG, prepare the following configuration.
The "src/atoms" part is optional.
src
atoms
svg
_index.scss // Definition of mixin
_names.scss // SVG Name Definition
svg.stories.module.scss // Styles for Stories
Svg.stories.tsx // Story Definition
Svg.tsx // React Component
Paste the following source code into each file and rewrite only the specified sections.
Set $css-modules
to true
only for CSS Modules.
@use "sass:meta";
@use "./names";
@forward "react-sass-inlinesvg" with ($css-modules: false, $svg-names: meta.module-variables("names"));
@forward "./names";
Define variables for the available SVG names.
$React: "React"; // Variable name and value must be the same
$Sass: "Sass";
$Svg: "Svg";
Copy and paste the following verbatim (no changes necessary).
@use "." as *;
.svg-catalog {
@include story-catalog;
}
Copy and paste the following verbatim (no changes necessary).
import React from "react";
import { renderStoryCatalog } from "react-sass-inlinesvg";
import { SVG, pathMap } from "./Svg";
import classNames from "./svg.module.scss";
export default { component: SVG };
export const Catalog = () =>
renderStoryCatalog(SVG, pathMap, classNames.svgCatalog);
Pass an object that defines the correspondence between SVG names and paths as the argument to setup()
.
import { setup, ExtractProps } from "react-sass-inlinesvg";
export type SVGProps = ExtractProps<typeof SVG>;
export const { SVG, pathMap } = setup({
React: () => "https://cdn.svgporn.com/logos/react.svg",
Sass: () => "https://cdn.svgporn.com/logos/sass.svg",
Svg: () => "https://cdn.svgporn.com/logos/svg.svg",
});
Launch Storybook and if you see SVG in the catalog, setup is complete.
Example of displaying SVG in a component called "Example."
src
atoms // atoms as in Setup example
...
example
example.module.scss // style definition
Example.stories.tsx // story definition
Example.tsx // component
- Place the
<SVG>
component - Pass
className
to the element
import React from "react";
import { SVG } from "../../atoms/svg/Svg";
import classNames from "./example.module.scss";
export const Example = () => {
return (
<div>
{/* Standard way to pass className to SVG. */}
<button className={classNames.reactButton}>
<SVG className={classNames.svg} /> React
</button>
<button className={classNames.sassButton}>
<SVG className={classNames.svg} /> Sass
</button>
{/* How to use SVG without passing className. */}
<button className={classNames.button}>
<SVG /> SVG
</button>
<button className={classNames.button}>
<SVG /> NULL
</button>
</div>
);
};
- Refer to
_index.scss
insvg
with@use
. - Specify the SVG you want to display using
@include
in the selector for<SVG>
.
// In `@use`, you can omit "/_index.scss" and the string after the trailing slash becomes a namespace.
// Please refer to the official documentation for the usage of the `@use` namespace in Sass.
// https://sass-lang.com/documentation/at-rules/use#choosing-a-namespace
@use "../../atoms/svg";
.react-button {
font-size: 48px;
.svg {
@include svg.show(svg.$React, 0.8em); // Display the React logo
}
&:hover .svg {
@include svg.show(
svg.$Sass
); // Hover over the button to display the Sass logo
}
&:active .svg {
@include svg.show(svg.$Svg); // Show the SVG logo when the button is pressed
}
}
.sass-button {
font-size: 48px;
.svg {
@include svg.show(svg.$Sass, 0.8em); // Display the Sass logo
}
&:hover .svg {
@include svg.show(svg.$Svg);
}
&:hover .svg,
// Selector for preventing flickering.(Moment after the hover ends, when the SVG has not yet been rewritten)
&:not(:hover) .svg[data-svg-name="#{svg.$Svg}"] {
fill: gray; // SVG changes color when hovering over a button.
}
&:active .svg {
@include svg.none; // When the button is pressed, the element does not maintain its area and becomes invisible.(`display: none` equivalent)
}
}
.button {
font-size: 48px;
&:nth-of-type(3) {
> svg {
@include svg.show(svg.$Svg, 0.8em); // Display the Sass logo
}
&:hover > svg {
@include svg.hidden; // When the button is pressed, the element is made invisible while maintaining its area.(`visibility: hidden` equivalent)
}
&:active > svg {
@include svg.null; // When the button is pressed, the entire element disappears and is not restored.
}
}
&:nth-of-type(4) {
> svg {
@include svg.null; // Do not display the element itself(Note that there are elements that are not visible for a moment after the initial drawing.)
}
}
}
- Add storybook stories.
import React from "react";
import { Example } from "./Example";
export default { component: Example };
export const Default = {};
If you start Storybook and the SVG is displayed, you have completed the usage check.
mixin | visibility | area | element |
---|---|---|---|
svg.show | ✅ | ✅ | ✅ |
svg.hidden | ❌ | ✅ | ✅ |
svg.hidden-opacity | ❌ | ✅ | ✅ |
svg.none | ❌ | ❌ | ✅ |
svg.null | ❌ | ❌ | ❌ |
Displays the specified SVG.
$svg-name {string}
SVG name defined in src/atoms/svg/_names.scss
OR "HIDDEN" "HIDDEN-OPACITY" "NONE" "NULL".
$args
Equivalent to show()
in smart-svg, except that $url
argument can be used.
content
If necessary, CSS properties can be written within the block to add display during loading.
The specified SVG is surrounded by a circular shape.
$svg-name {string}
SVG name defined in src/atoms/svg/_names.scss
OR "HIDDEN" "HIDDEN-OPACITY" "NONE" "NULL".
$args
Equivalent to show-circle()
in smart-svg, except $url
and $fill-image
arguments can be used.
content
If necessary, CSS properties can be written within the block to add display during loading.
The specified SVG is surrounded by a rectangle shape.
$svg-name {string}
SVG name defined in src/atoms/svg/_names.scss
OR "HIDDEN" "HIDDEN-OPACITY" "NONE" "NULL".
$args
Equivalent to show-square()
in smart-svg, except $url
and $fill-image
arguments can be used.
content
If necessary, CSS properties can be written within the block to add display during loading.
@include svg.hidden($size: null);
Like visibility: hidden
, it will be invisible with the area of the element reserved.
It will not respond to :hover
pseudo-selectors.
$size {string} ▶︎ null
The value used for the width
height
CSS property.
If omitted, width
height
will not be set.
@include svg.hidden-opacity($size: null);
Use opacity: 0
to make the element invisible with the area of the element reserved.
It also responds to :hover
pseudo-selectors.
$size {string} ▶︎ null
The value used for the width
height
CSS property.
If omitted, width
height
will not be set.
Like display: none
, the element is hidden with no area.
$size {string} ▶︎ null
The value used for the width
height
CSS property.
If omitted, width
height
will not be set.
The <svg>
element itself will not be output, just as if you had returned null
in a React component.
**However, the <svg>
element will not be visible after that. **
Note that if you specify @include svg.null
from the CSS selector side, an invisible element will be drawn for a moment.
This may affect +
:first-child
:last-child
:nth-*
:empty
, etc.
This is a mixin that provides styles for the Story catalog.
When the SVG component sets the available SVG information, it returns the component and the pathMap passed as arguments.
pathMap {{[string]: () => string}}
A map of functions that return SVG names and paths.
{
FooIcon: () => "https://.../foo-icon.svg",
BarIcon: () => "https://.../bar-icon.svg",
}
options.fetchOptions {RequestInit}
Custom options for the request.
options.uniqueHash {string} ▶︎ a random 8 characters string [A-Za-z0-9]
A string to use with uniquifyIDs
.
options.uniquifyIDs {boolean} ▶︎ false
Create unique IDs for each icon.
The type from which the Props type of the SVG component is extracted.
type SVGProps = ExtractProps<typeof SVG>;
Function to draw a catalog of stories.
Based on React.SVGProps<SVGSVGElement>
.
defaultName {string}
SVG name for initial rendering.
Available when there is no need to switch SVGs and no need to specify it on the Sass side.
If defaultName
is "NULL"
, unlike @include svg.null;
, the element is not output from the first drawing.
description {string}
A description for your SVG. It will override an existing <desc>
tag.
innerRef {React.Ref}
Set a ref in SVGElement.
onLoad {function}
A callback to be invoked upon successful load.
This will receive 2 arguments: the src
prop and a hasCache
boolean
onError {function}
A callback to be invoked if loading the SVG fails. This will receive a single argument with:
a FetchError
with:
{
message: string;
type: string;
errno: string;
code: string;
}
or an Error, which has the following properties:
{
message: string;
}
title {string}
A title for your SVG. It will override an existing <title>
tag.
- Draw
<svg>
with empty<svg>
.<svg className="svg" aria-busy="true"></svg>
- Add
<style>
containing@keyframes
to<head>
. - Start listening for animation.
<svg className="svg" aria-busy="true" data-svg-status="loading"></svg>
- The
animation
event of<svg>
element's::before
fires. - Event handling.
- Extract SVG names from
event.animationName
. - Resolve URL from SVG name and fetch
<svg className="svg" aria-busy="true" data-svg-status="loading" data-svg-name="FooIcon"></svg>
- Extract SVG names from
- Reflect the acquired SVG content in the element
<svg className="svg" data-svg-status="complete" data-svg-name="FooIcon">*</svg>
No change in ref
will occur as the state or content of the SVG changes, since we will always use a single svg element.
If you want to control the style in detail according to the state of the SVG before it completes loading, please refer to the following.
@use "../../atoms/svg";
.svg {
@include svg.show(svg.$React) {
// The style described here will be used for display during loading.
}
&[aria-busy="true"] {
// After drawing the element - before animationstart event listening starts.
&[data-svg-status="loading"] {
// After animationstart event listening starts - Before animationstart event processing.
&[data-svg-name] {
// After animationstart event is processed - before SVG content is reflected.
}
}
}
&[data-svg-status="complete"] {
// After reflecting SVG contents.
}
&[data-svg-status="error"] {
// on error.
}
}
In react-sass-inlinesvg, there is a momentary time lag due to the mechanism of switching SVG via animationstart.
Therefore, in .sass-button {}
in the src/example/example.module.scss
example, if you try to change the color when the SVG switches on hover as shown below, the SVG and color switching timing will not match, causing a flicker.
.sass-button {
// ...
&:hover .svg * {
fill: gray; // SVG changes color when hovering over a button.
}
}
This can be handled by applying a style that specifies that the hover condition has changed and the SVG has not yet switched.
.sass-button {
// ...
&:hover .svg *,
// Selector for preventing flickering.(Moment after the hover ends, when the SVG has not yet been rewritten)
&:not(:hover) .svg[data-svg-name="#{svg.$Svg}"] * {
fill: gray; // SVG changes color when hovering over a button.
}
}
Any browsers that support inlining SVGs and fetch and animationstart will work.
If you need to support legacy browsers you'll need to include a polyfiil for fetch
and Number.isNaN
in your app. Take a look at react-app-polyfill or polyfill.io.
If you are loading remote SVGs, you'll need to make sure it has CORS support.
react-inlinesvgIn addition to the reasons given in why-you-need-this-package, it is beneficial when you want to control which SVGs are displayed from your Sass.
Using react-sass-inlinesvg provides the following benefits.
- Eliminates the need to write JS logic to switch between SVGs based on various conditions, such as
:hover
. - JSX improves component reusability by eliminating the need to determine specific SVGs.
- React processing costs are reduced by a design that cuts wasteful processing as much as possible.
- Significant performance differences, especially when displaying large numbers of SVGs.
- Significant performance differences, especially for large displays of overlapping SVGs.
- When using react-sass-inlinesvg, SVG must be switched with Sass
- If I have two SVG components and switch between them in JSX, I have a problem with elements disappearing momentarily.