- Published on
Dive In: A Step-by-Step Guide to Creating a CSS Fish Scale Pattern
- Authors
- Name
- Md Nasim Sheikh
- @nasimStg
'Dive In: A Step-by-Step Guide to Creating a CSS Fish Scale Pattern'
Learn how to craft a beautiful, responsive fish scale background pattern using only CSS. This comprehensive guide covers multiple techniques, from pure CSS radial gradients to advanced SVG integration.
Table of Contents
- 'Dive In: A Step-by-Step Guide to Creating a CSS Fish Scale Pattern'
- Why Bother with CSS for Patterns?
- The Core Concept: Deconstructing the Fish Scale
- Method 1: The Radial Gradient Approach (The "Purest" CSS Method)
- Step 1: Creating a Single "Scale"
- Step 2: Tiling the Scales in a Row
- Step 3: Creating the Overlap with Multiple Layers
- Method 2: The Pseudo-Element Approach
- How It Works
- Method 3: The SVG Background Approach
- Step 1: Creating the SVG Pattern
- Step 2: Using the SVG in CSS
- Advanced Techniques & Best Practices
- Adding Animation
- Responsiveness
- Accessibility Considerations
- Conclusion
In the world of web design, the details make the difference. While bold typography and striking imagery capture initial attention, subtle textures and background patterns provide the depth and polish that elevate a good design to a great one. One such pattern, both elegant and whimsical, is the "fish scale" or "scallop" pattern.
It evokes everything from art deco glamour to serene underwater scenes. You might think creating such an intricate, overlapping pattern would require a graphics editor and an image file. But what if I told you that you can craft a perfectly scalable, customizable, and lightweight fish scale pattern using nothing but the power of CSS?
In this comprehensive guide, we'll dive deep into the techniques for creating this beautiful pattern from scratch. We'll explore multiple methods, from the pure CSS radial-gradient
approach to using pseudo-elements and even embedding SVGs. By the end, you'll not only have a stunning pattern for your projects but also a deeper understanding of what's possible with modern CSS.
Why Bother with CSS for Patterns?
Before we jump into the code, let's quickly touch on why you'd choose CSS over a traditional .png
or .jpg
background image. The benefits are significant:
- Performance: A CSS pattern is just code. There are no extra HTTP requests to fetch an image file, which means faster page loads and a better Lighthouse score. The browser renders it on the fly.
- Scalability: CSS patterns are vector-based. They will look crisp and perfect on any screen, from a standard monitor to a 5K retina display. There's no pixelation, ever.
- Customization: This is the real superpower. Want to change the color of the scales to match your brand? It's a one-line change in your CSS. Want to make the scales bigger or smaller? Just tweak a CSS variable. This level of dynamic control is impossible with a static image file.
- Maintainability: Keeping your design logic within your stylesheet simplifies your project structure. You don't have to hunt for
pattern-v2-final-final.png
when you need to make a change.
Convinced? Great. Let's get our hands dirty.
The Core Concept: Deconstructing the Fish Scale
The key to creating any complex pattern in CSS is to break it down into its simplest, repeatable unit. At first glance, the fish scale pattern looks complex with its overlapping curves.
But what is it really? It's just a grid of circles, offset from each other.
Imagine two rows of circles:
Row 1: O O O O
Row 2: O O O O
When you stack them, the second row's circles nestle perfectly into the gaps of the first row. The "scale" shape we see is simply the top half of each circle. Our strategy will be to use CSS to create a semi-circle shape and then tile it in this specific offset grid.
We'll primarily be using the background
property, which is surprisingly powerful. It allows us to layer multiple backgrounds on top of each other, each with its own image, size, and position. This layering is the magic that will bring our pattern to life.
Method 1: The Radial Gradient Approach (The "Purest" CSS Method)
This is the most common and, in many ways, the most elegant pure CSS solution. We'll use the radial-gradient()
function to draw our circles directly in the CSS. It might seem like an unusual tool for the job, but it's perfect for creating simple shapes.
Step 1: Creating a Single "Scale"
First, let's create a single scale. A radial-gradient
creates an image that transitions between colors radiating out from a central point. To make a solid circle, we just need a sharp transition from one color to transparent.
Here's how we can draw a blue semi-circle that will act as our scale:
.element-with-scale {
background-image: radial-gradient(circle at 50% 100%, transparent 50%, #3498db 50%);
}
Let's break that down:
radial-gradient(...)
: This is our function.circle at 50% 100%
: This defines the shape and position of the gradient. We're saying, "Make a circular gradient, and its center should be at 50% from the left and 100% from the top (i.e., the bottom-center of its container)."transparent 50%
: The first color stop. From the center out to 50% of the radius, the gradient is transparent.#3498db 50%
: The second color stop. At exactly the 50% mark, the color abruptly changes to our blue (#3498db
) and stays that color to the edge.
By placing the gradient's center at the bottom (50% 100%
), only the top half of the resulting circle is visible within the background's box, effectively giving us our semi-circle scale shape.
Step 2: Tiling the Scales in a Row
Now we have one scale, but we need a whole row of them. This is where background-size
and background-repeat
come in. By default, a background image will repeat. We just need to control the size of each repeated instance.
.scale-row {
background-image: radial-gradient(circle at 50% 100%, transparent 50%, #3498db 50%);
/* Let's define the size of each scale */
background-size: 50px 50px; /* Each scale will be 50px wide and 50px high */
}
If you apply this to a div
, you'll now see a repeating row of our semi-circles. We're getting closer!
Step 3: Creating the Overlap with Multiple Layers
This is where the magic happens. To create the overlapping, staggered effect, we need two rows of scales, with one row offset from the other. CSS allows us to define multiple background layers by simply separating them with a comma.
The first background listed is the top layer, and the last is the bottom layer.
Our plan:
- Layer 1 (Top): A row of scales.
- Layer 2 (Bottom): Another row of scales, shifted down and to the right.
.fish-scale-pattern {
background-color: #f0f8ff; /* A nice light blue background */
/* Let's define our scale size once */
--scale-size: 50px;
/* Multiple background layers, separated by commas */
background-image:
/* Layer 1: The first row of scales */
radial-gradient(circle at 50% 0, transparent var(--scale-size), #3498db var(--scale-size)),
/* Layer 2: The second, offset row of scales */
radial-gradient(circle at 0 50%, transparent var(--scale-size), #3498db var(--scale-size));
/* Define the size for EACH background layer */
background-size: calc(var(--scale-size) * 2) calc(var(--scale-size) * 2);
/* Define the position for EACH background layer */
background-position: 0 0, var(--scale-size) var(--scale-size);
}
Whoa, that's a lot. Let's dissect it carefully:
CSS Variable: We've introduced
--scale-size
to make our pattern easily configurable. Let's say it's25px
for this explanation.background-image
:- The first
radial-gradient
creates our primary row of scales. We've changed the position to50% 0
(top-center) and the radius to be based on our variable. This makes the bottom half of the circle visible. - The second
radial-gradient
is identical. It will serve as our offset row.
- The first
background-size
: This is crucial. We set the size of the repeating tile for both layers to be100px 100px
(since--scale-size
is50px
). This gives each scale enough room to be positioned within its tile.background-position
: This is the key to the offset.0 0
: The first layer (our top row) is positioned at the top-left corner of each100px
tile.var(--scale-size) var(--scale-size)
which is50px 50px
: The second layer is positioned50px
from the left and50px
from the top within its100px
tile. This half-width, half-height shift is what nestles the second row of scales perfectly into the gaps of the first.
Here is a more refined and complete example you can drop into your project:
<div class="scales-background"></div>
.scales-background {
--scale-color: #2980b9;
--bg-color: #ecf0f1;
--scale-size: 30px; /* Controls the size of the scales */
min-height: 400px;
width: 100%;
background-color: var(--bg-color);
/* The two layers for the pattern */
background-image:
radial-gradient(circle at 50% 0,
rgba(0,0,0,0) var(--scale-size),
var(--scale-color) calc(var(--scale-size) + 1px)
),
radial-gradient(circle at 100% 50%,
rgba(0,0,0,0) var(--scale-size),
var(--scale-color) calc(var(--scale-size) + 1px)
);
background-size: calc(var(--scale-size) * 2) calc(var(--scale-size) * 2);
background-position: calc(var(--scale-size) / 2) var(--scale-size), 0 0;
}
Note: I've slightly adjusted the logic here to a common alternative positioning which can feel more intuitive. The core principle of offset layers remains the same. Experiment to see what clicks for you! The beauty of CSS is that there are often multiple paths to the same result.
Method 2: The Pseudo-Element Approach
While the multiple background approach is powerful, it takes over the entire background
property of an element. What if you want to lay this pattern on top of another background, like a photo or a gradient?
This is a perfect use case for pseudo-elements like ::before
or ::after
.
The idea is to create a pseudo-element that covers its parent element and then apply the fish scale pattern to the pseudo-element's background. This leaves the parent element's background
free for other uses.
How It Works
Let's say we have a section we want to give a textured overlay.
<section class="hero-section">
<h1>Welcome to the Deep</h1>
</section>
.hero-section {
position: relative; /* Essential for positioning the pseudo-element */
background-image: url('underwater-photo.jpg');
background-size: cover;
padding: 5rem 2rem;
color: white;
text-align: center;
overflow: hidden; /* Prevents pseudo-element from spilling out */
}
.hero-section::before {
content: ''; /* Pseudo-elements must have a content property */
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* --- Apply the fish scale pattern here! --- */
--scale-color: rgba(41, 128, 185, 0.4); /* Use a semi-transparent color */
--scale-size: 40px;
background-image:
radial-gradient(circle at 50% 0,
transparent var(--scale-size),
var(--scale-color) var(--scale-size)
),
radial-gradient(circle at 100% 50%,
transparent var(--scale-size),
var(--scale-color) var(--scale-size)
);
background-size: calc(var(--scale-size) * 2) calc(var(--scale-size) * 2);
background-position: calc(var(--scale-size) / 2) var(--scale-size), 0 0;
z-index: 0; /* Ensures it's behind the content but above the background */
}
/* Ensure the content is on top of the overlay */
.hero-section > * {
position: relative;
z-index: 1;
}
In this example:
- The
.hero-section
has its own background image. - We set
position: relative
on it so we can position the::before
pseudo-element relative to it. - The
::before
element is stretched to cover the entire parent usingposition: absolute
and settingtop
,left
,width
, andheight
. - The exact same fish scale pattern code from Method 1 is applied to the
::before
element. - We use a semi-transparent
rgba()
color for the scales so the underlying background photo is still visible. z-index
is used to manage the stacking, ensuring the text (h1
) appears above the pattern overlay.
This technique is incredibly useful for adding non-intrusive, decorative textures to components without complicating their primary styling.
Method 3: The SVG Background Approach
For ultimate flexibility and sometimes cleaner code, we can turn to SVG (Scalable Vector Graphics). We can define our pattern inside an SVG and then use that SVG as a background-image
in our CSS.
Pros of this method:
- Separation of Concerns: The logic for drawing the pattern is contained within the SVG, making the CSS cleaner.
- Complex Shapes: SVGs can define much more complex shapes than CSS gradients if you wanted, for example, scales with unique outlines or internal gradients.
- Performance: A well-defined SVG pattern can be highly performant.
Step 1: Creating the SVG Pattern
An SVG isn't just an image format; it's an XML-based document. We can define a pattern within it that will be tiled.
Here is the code for a fish-scale.svg
file:
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- Define the pattern -->
<pattern id="fish-scale" patternUnits="userSpaceOnUse" width="50" height="50">
<!-- The two circles that create the pattern -->
<g fill="#2980b9">
<circle cx="25" cy="25" r="25"></circle>
<circle cx="0" cy="25" r="25"></circle>
</g>
</pattern>
</defs>
<!-- Apply the pattern to a rectangle that fills the SVG -->
<rect width="100%" height="100%" fill="url(#fish-scale)"></rect>
</svg>
Let's break this down:
<defs>
: A section for defining elements that will be used later.<pattern>
: This is the core element. We give it anid
(fish-scale
), define itswidth
andheight
(the size of the repeating tile), and setpatternUnits="userSpaceOnUse"
to use absolute coordinates.<g>
: A group element to apply a fill color to both circles at once.<circle>
: We draw two circles. Their positions relative to each other inside the50x50
pattern tile create the overlapping effect.<rect>
: Finally, we draw a rectangle that fills the entire SVG viewport and apply our pattern to it usingfill="url(#fish-scale)"
.
Step 2: Using the SVG in CSS
There are two ways to use this SVG in your CSS.
A) As an External File:
Save the code above as fish-scale.svg
in your project's assets folder. Then, in your CSS:
.element-with-svg-pattern {
background-image: url('/path/to/your/assets/fish-scale.svg');
background-color: #ecf0f1;
}
This is clean and simple. The downside is that it requires an extra HTTP request to fetch the SVG file.
B) As a Data URI:
For maximum performance, you can embed the SVG directly into your CSS using a Data URI. This eliminates the extra HTTP request. First, you need to URL-encode your SVG code.
Your SVG code: <svg ...><defs>...</defs>...</svg>
Becomes a long, single-line string. You can use an online tool (search for "SVG to Data URI encoder") to do this easily. The result will look something like this (shortened for brevity):
.element-with-svg-pattern {
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3cdefs%3e%3cpattern id='fish-scale' patternUnits='userSpaceOnUse' width='50' height='50'%3e%3cg fill='%232980b9'%3e%3ccircle cx='25' cy='25' r='25'%3e%3c/circle%3e%3ccircle cx='0' cy='25' r='25'%3e%3c/circle%3e%3c/g%3e%3c/pattern%3e%3c/defs%3e%3crect width='100%25' height='100%25' fill='url(%23fish-scale)'%3e%3c/rect%3e%3c/svg%3e");
}
While this looks messy in your CSS file, it's incredibly efficient. This is the technique used by many popular CSS frameworks and libraries (like Hero Patterns) to provide patterns without extra file downloads.
Advanced Techniques & Best Practices
Now that you've mastered the creation, let's talk about polishing your pattern.
Adding Animation
A subtle animation can bring your pattern to life, creating a shimmering water effect. We can achieve this by animating the background-position
.
@keyframes move-scales {
from {
background-position: 0 0;
}
to {
background-position: -100px 50px;
}
}
.animated-scales {
/* Apply your fish scale pattern here first */
/* ... */
animation: move-scales 10s linear infinite;
}
A word of caution: Animations on background-position
can be computationally expensive. Use them sparingly and test for performance. Always respect user preferences with the prefers-reduced-motion
media query:
@media (prefers-reduced-motion: no-preference) {
.animated-scales {
animation: move-scales 10s linear infinite;
}
}
Responsiveness
Both the CSS gradient and SVG methods are inherently responsive and scalable. The pattern will tile to fill any container size. If you want the scales themselves to change size on different devices (e.g., smaller scales on mobile), you can simply change the value of your CSS custom property within a media query:
/* Default scale size */
:root {
--scale-size: 30px;
}
/* Smaller scales on screens less than 600px wide */
@media (max-width: 600px) {
:root {
--scale-size: 20px;
}
}
Accessibility Considerations
Patterns are beautiful, but they can also be distracting. When using a background pattern, especially behind text, ensure there is sufficient contrast.
- High Contrast: The color difference between your text and the average color of the pattern behind it must meet WCAG guidelines (at least 4.5:1 for normal text).
- Decorative Use: Often, the safest place for a complex pattern is on a purely decorative element where there is no text to read.
- Subtlety is Key: A low-contrast, subtle pattern is often more effective and accessible than a loud, high-contrast one.
Conclusion
We've journeyed from a simple idea—a grid of circles—to a complete, production-ready CSS fish scale pattern. We've seen how to craft it with pure CSS radial-gradient
, how to layer it as a non-intrusive overlay with ::before
, and how to define it cleanly with SVG.
What started as a design challenge becomes a testament to the flexibility of modern CSS. By understanding the core principles of background layers, sizing, and positioning, you've unlocked the ability to create not just this pattern, but countless others.
So go ahead, take these code snippets and experiment. Change the colors, tweak the sizes, animate the position. The web is your canvas, and you now have a powerful new brush in your toolkit. Happy coding!