Real-World Project: Building a Complete Website Stylesheet
Project Overview: Portfolio Website Stylesheet
In this comprehensive, hands-on lesson, we'll build a complete, production-ready stylesheet for a portfolio/landing page from scratch using SASS. This project will consolidate everything you've learned throughout this course, applying best practices, proper architecture, responsive design, and modern CSS techniques. By the end, you'll have a fully functional, maintainable stylesheet that you can use as a template for future projects.
Project Requirements
Our portfolio website will include:
- Responsive navigation header with logo and menu
- Hero section with call-to-action
- About section with image and text
- Skills/services cards section
- Portfolio/projects gallery grid
- Contact form section
- Footer with social links
- Dark mode toggle support
- Responsive design (mobile-first)
- Smooth animations and transitions
Step 1: Setting Up the 7-1 Folder Structure
We'll use the industry-standard 7-1 architecture pattern for organizing our SASS files.
Project Structure
portfolio/
├── scss/
│ ├── abstracts/
│ │ ├── _variables.scss
│ │ ├── _mixins.scss
│ │ └── _functions.scss
│ ├── base/
│ │ ├── _reset.scss
│ │ ├── _typography.scss
│ │ └── _animations.scss
│ ├── components/
│ │ ├── _buttons.scss
│ │ ├── _card.scss
│ │ ├── _form.scss
│ │ └── _social-links.scss
│ ├── layout/
│ │ ├── _header.scss
│ │ ├── _footer.scss
│ │ ├── _navigation.scss
│ │ ├── _grid.scss
│ │ └── _section.scss
│ ├── pages/
│ │ └── _home.scss
│ ├── themes/
│ │ └── _dark-mode.scss
│ ├── vendors/
│ │ └── (third-party styles if needed)
│ └── main.scss
├── dist/
│ └── css/
│ └── style.css
└── package.json
Step 2: Creating the Variables File
Our variables file will define all design tokens: colors, typography, spacing, breakpoints, and transitions.
abstracts/_variables.scss
// ========================================
// Color System
// ========================================
// Primary brand colors
$color-primary: #6366f1;
$color-primary-light: #818cf8;
$color-primary-dark: #4f46e5;
// Neutral colors
$color-white: #ffffff;
$color-black: #000000;
$color-gray-100: #f3f4f6;
$color-gray-200: #e5e7eb;
$color-gray-300: #d1d5db;
$color-gray-400: #9ca3af;
$color-gray-500: #6b7280;
$color-gray-600: #4b5563;
$color-gray-700: #374151;
$color-gray-800: #1f2937;
$color-gray-900: #111827;
// Semantic colors
$color-success: #10b981;
$color-warning: #f59e0b;
$color-error: #ef4444;
$color-info: #3b82f6;
// Background colors
$bg-light: $color-white;
$bg-light-alt: $color-gray-100;
$bg-dark: $color-gray-900;
$bg-dark-alt: $color-gray-800;
// Text colors
$text-light: $color-gray-900;
$text-light-secondary: $color-gray-600;
$text-dark: $color-gray-100;
$text-dark-secondary: $color-gray-400;
// ========================================
// Typography
// ========================================
// Font families
$font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
$font-heading: 'Poppins', $font-primary;
$font-mono: 'Fira Code', 'Courier New', monospace;
// Font sizes
$font-size-xs: 0.75rem; // 12px
$font-size-sm: 0.875rem; // 14px
$font-size-base: 1rem; // 16px
$font-size-lg: 1.125rem; // 18px
$font-size-xl: 1.25rem; // 20px
$font-size-2xl: 1.5rem; // 24px
$font-size-3xl: 1.875rem; // 30px
$font-size-4xl: 2.25rem; // 36px
$font-size-5xl: 3rem; // 48px
$font-size-6xl: 3.75rem; // 60px
// Font weights
$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-semibold: 600;
$font-weight-bold: 700;
// Line heights
$line-height-tight: 1.25;
$line-height-normal: 1.5;
$line-height-relaxed: 1.75;
// ========================================
// Spacing Scale
// ========================================
$spacing-0: 0;
$spacing-1: 0.25rem; // 4px
$spacing-2: 0.5rem; // 8px
$spacing-3: 0.75rem; // 12px
$spacing-4: 1rem; // 16px
$spacing-5: 1.25rem; // 20px
$spacing-6: 1.5rem; // 24px
$spacing-8: 2rem; // 32px
$spacing-10: 2.5rem; // 40px
$spacing-12: 3rem; // 48px
$spacing-16: 4rem; // 64px
$spacing-20: 5rem; // 80px
$spacing-24: 6rem; // 96px
// ========================================
// Breakpoints
// ========================================
$breakpoint-sm: 640px;
$breakpoint-md: 768px;
$breakpoint-lg: 1024px;
$breakpoint-xl: 1280px;
$breakpoint-2xl: 1536px;
// Breakpoints map for loop usage
$breakpoints: (
'sm': $breakpoint-sm,
'md': $breakpoint-md,
'lg': $breakpoint-lg,
'xl': $breakpoint-xl,
'2xl': $breakpoint-2xl
);
// ========================================
// Layout
// ========================================
$container-max-width: 1280px;
$container-padding: $spacing-6;
$header-height: 70px;
$header-height-mobile: 60px;
// ========================================
// Border & Radius
// ========================================
$border-width: 1px;
$border-color-light: $color-gray-200;
$border-color-dark: $color-gray-700;
$border-radius-sm: 0.25rem; // 4px
$border-radius-base: 0.5rem; // 8px
$border-radius-lg: 0.75rem; // 12px
$border-radius-xl: 1rem; // 16px
$border-radius-full: 9999px;
// ========================================
// Shadows
// ========================================
$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
$shadow-base: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
$shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
// ========================================
// Transitions
// ========================================
$transition-fast: 150ms ease-in-out;
$transition-base: 250ms ease-in-out;
$transition-slow: 350ms ease-in-out;
// ========================================
// Z-index Scale
// ========================================
$z-index-dropdown: 1000;
$z-index-sticky: 1020;
$z-index-fixed: 1030;
$z-index-modal-backdrop: 1040;
$z-index-modal: 1050;
$z-index-popover: 1060;
$z-index-tooltip: 1070;
Step 3: Building Reusable Mixins
Create mixins for responsive design, flexbox layouts, and common patterns.
abstracts/_mixins.scss
@use 'variables' as *;
// ========================================
// Responsive Breakpoints
// ========================================
@mixin respond-to($breakpoint) {
@if map-has-key($breakpoints, $breakpoint) {
@media (min-width: map-get($breakpoints, $breakpoint)) {
@content;
}
} @else {
@warn "Invalid breakpoint: #{$breakpoint}";
}
}
// Custom breakpoint
@mixin custom-breakpoint($min-width) {
@media (min-width: $min-width) {
@content;
}
}
// ========================================
// Flexbox Utilities
// ========================================
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
@mixin flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
@mixin flex-column {
display: flex;
flex-direction: column;
}
// ========================================
// Grid Utilities
// ========================================
@mixin grid($columns: 3, $gap: $spacing-6) {
display: grid;
grid-template-columns: repeat($columns, 1fr);
gap: $gap;
}
@mixin grid-responsive($mobile: 1, $tablet: 2, $desktop: 3, $gap: $spacing-6) {
display: grid;
grid-template-columns: repeat($mobile, 1fr);
gap: $gap;
@include respond-to('md') {
grid-template-columns: repeat($tablet, 1fr);
}
@include respond-to('lg') {
grid-template-columns: repeat($desktop, 1fr);
}
}
// ========================================
// Positioning
// ========================================
@mixin absolute-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@mixin absolute-cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
// ========================================
// Text Utilities
// ========================================
@mixin text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin line-clamp($lines: 2) {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
// ========================================
// Visual Effects
// ========================================
@mixin card-shadow {
box-shadow: $shadow-base;
transition: box-shadow $transition-base;
&:hover {
box-shadow: $shadow-lg;
}
}
@mixin focus-ring($color: $color-primary) {
outline: 2px solid transparent;
outline-offset: 2px;
&:focus-visible {
outline-color: $color;
}
}
Step 4: Base Styles
Set up the foundation with reset, typography, and global styles.
base/_reset.scss
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
body {
min-height: 100vh;
line-height: 1.5;
}
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
}
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
ul,
ol {
list-style: none;
}
a {
text-decoration: none;
color: inherit;
}
base/_typography.scss
@use '../abstracts' as *;
body {
font-family: $font-primary;
font-size: $font-size-base;
font-weight: $font-weight-normal;
line-height: $line-height-normal;
color: $text-light;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: $font-heading;
font-weight: $font-weight-bold;
line-height: $line-height-tight;
margin-bottom: $spacing-4;
}
h1 {
font-size: $font-size-4xl;
@include respond-to('md') {
font-size: $font-size-5xl;
}
@include respond-to('lg') {
font-size: $font-size-6xl;
}
}
h2 {
font-size: $font-size-3xl;
@include respond-to('md') {
font-size: $font-size-4xl;
}
}
h3 {
font-size: $font-size-2xl;
@include respond-to('md') {
font-size: $font-size-3xl;
}
}
h4 {
font-size: $font-size-xl;
}
h5 {
font-size: $font-size-lg;
}
h6 {
font-size: $font-size-base;
}
p {
margin-bottom: $spacing-4;
&:last-child {
margin-bottom: 0;
}
}
.lead {
font-size: $font-size-lg;
color: $text-light-secondary;
@include respond-to('md') {
font-size: $font-size-xl;
}
}
Step 5: Building Components
Create reusable component styles for buttons, cards, and forms.
components/_buttons.scss
@use '../abstracts' as *;
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: $spacing-2;
padding: $spacing-3 $spacing-6;
font-weight: $font-weight-medium;
border-radius: $border-radius-base;
border: $border-width solid transparent;
cursor: pointer;
transition: all $transition-base;
@include focus-ring;
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.btn-primary {
background-color: $color-primary;
color: $color-white;
&:hover:not(:disabled) {
background-color: $color-primary-dark;
transform: translateY(-2px);
box-shadow: $shadow-md;
}
&:active:not(:disabled) {
transform: translateY(0);
}
}
.btn-secondary {
background-color: transparent;
color: $color-primary;
border-color: $color-primary;
&:hover:not(:disabled) {
background-color: $color-primary;
color: $color-white;
}
}
.btn-ghost {
background-color: transparent;
color: $text-light;
&:hover:not(:disabled) {
background-color: $color-gray-100;
}
}
.btn-lg {
padding: $spacing-4 $spacing-8;
font-size: $font-size-lg;
}
.btn-sm {
padding: $spacing-2 $spacing-4;
font-size: $font-size-sm;
}
components/_card.scss
@use '../abstracts' as *;
.card {
background-color: $bg-light;
border-radius: $border-radius-lg;
padding: $spacing-6;
@include card-shadow;
&__image {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: $border-radius-base;
margin-bottom: $spacing-4;
}
&__title {
font-size: $font-size-xl;
margin-bottom: $spacing-3;
}
&__text {
color: $text-light-secondary;
margin-bottom: $spacing-4;
}
&__footer {
@include flex-between;
padding-top: $spacing-4;
border-top: $border-width solid $border-color-light;
}
}
Step 6: Layout Components
layout/_header.scss
@use '../abstracts' as *;
.header {
position: sticky;
top: 0;
z-index: $z-index-sticky;
background-color: $bg-light;
border-bottom: $border-width solid $border-color-light;
backdrop-filter: blur(10px);
background-color: rgba(255, 255, 255, 0.9);
&__container {
max-width: $container-max-width;
margin: 0 auto;
padding: 0 $container-padding;
@include flex-between;
height: $header-height-mobile;
@include respond-to('md') {
height: $header-height;
}
}
&__logo {
font-size: $font-size-xl;
font-weight: $font-weight-bold;
color: $color-primary;
}
&__nav {
display: none;
@include respond-to('md') {
display: flex;
gap: $spacing-8;
}
}
&__nav-link {
font-weight: $font-weight-medium;
transition: color $transition-base;
&:hover {
color: $color-primary;
}
}
}
Step 7: Responsive Design Implementation
Apply mobile-first responsive design throughout the project.
pages/_home.scss
@use '../abstracts' as *;
.hero {
padding: $spacing-16 $container-padding;
text-align: center;
@include respond-to('lg') {
padding: $spacing-24 $container-padding;
}
&__title {
margin-bottom: $spacing-6;
background: linear-gradient(135deg, $color-primary 0%, $color-primary-light 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
&__buttons {
display: flex;
flex-direction: column;
gap: $spacing-4;
max-width: 400px;
margin: 0 auto;
@include respond-to('sm') {
flex-direction: row;
justify-content: center;
}
}
}
.skills {
padding: $spacing-20 $container-padding;
background-color: $bg-light-alt;
&__grid {
max-width: $container-max-width;
margin: 0 auto;
@include grid-responsive($mobile: 1, $tablet: 2, $desktop: 3);
}
}
Step 8: Dark Mode Support
themes/_dark-mode.scss
@use '../abstracts' as *;
[data-theme="dark"] {
color-scheme: dark;
body {
background-color: $bg-dark;
color: $text-dark;
}
.header {
background-color: rgba(17, 24, 39, 0.9);
border-bottom-color: $border-color-dark;
}
.card {
background-color: $bg-dark-alt;
color: $text-dark;
}
.btn-ghost {
color: $text-dark;
&:hover:not(:disabled) {
background-color: $color-gray-800;
}
}
.skills {
background-color: $bg-dark-alt;
}
}
Step 9: Final Compilation and Optimization
main.scss - Main Entry File
// Abstracts
@use 'abstracts/variables';
@use 'abstracts/mixins';
@use 'abstracts/functions';
// Base
@use 'base/reset';
@use 'base/typography';
@use 'base/animations';
// Layout
@use 'layout/header';
@use 'layout/footer';
@use 'layout/navigation';
@use 'layout/grid';
@use 'layout/section';
// Components
@use 'components/buttons';
@use 'components/card';
@use 'components/form';
@use 'components/social-links';
// Pages
@use 'pages/home';
// Themes
@use 'themes/dark-mode';
package.json - Build Scripts
{
"scripts": {
"dev": "sass --watch scss/main.scss:dist/css/style.css --source-map",
"build": "sass scss/main.scss:dist/css/style.css --style=compressed --no-source-map",
"build:dev": "sass scss/main.scss:dist/css/style.css --style=expanded --source-map"
}
}
Exercise 1: Complete the Project
Build the complete portfolio stylesheet:
- Set up the 7-1 folder structure
- Create all the files shown in this lesson
- Add a footer component with social links
- Create a contact form component
- Implement smooth scroll animations
- Test dark mode toggle
- Verify responsive design on all breakpoints
- Build for production and check the output file size
Exercise 2: Extend the Project
Add these additional features:
- Create a blog card component for a blog section
- Add a testimonials carousel section
- Implement a hamburger menu for mobile navigation
- Add hover effects and transitions to all interactive elements
- Create utility classes for spacing, colors, and typography
Exercise 3: Optimize and Audit
Optimize your stylesheet:
- Run the build command and check the CSS file size
- Use a CSS analyzer to identify unused styles
- Remove any redundant code or unused variables
- Set up PostCSS with autoprefixer and cssnano
- Test the final build in multiple browsers
- Document your color system and component API