# Technical Documentation: Chalarangelo/30-seconds-of-code

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/Chalarangelo/30-seconds-of-code)

### Overview (README.md)

[](https://30secondsofcode.org/js/p/1)

# 30 seconds of code

> Coding articles to level up your development skills

* Visit [the website](https://30secondsofcode.org) to view all articles.
* Search for articles and collections that suit your needs, using the name, tags, language or description.
* Browse all [articles](https://30secondsofcode.org/snippets/p/1) or individual [collections](https://30secondsofcode.org/collections/p/1) for each topic.
* Click on each card to view the whole article, including code snippets, explanation and examples.
* If you like the project, give it a star. It means a lot.

## Want to contribute?

* New content contributions are not accepted at this time.
* You can, however, report problems with the website or suggest improvements by [opening an issue](https://github.com/Chalarangelo/30-seconds-of-code/issues/new).

## Credits

* This repository is maintained by [Angelos Chalaris](https://github.com/Chalarangelo).
* All code snippets are licensed under the [CC-BY-4.0 License](https://creativecommons.org/licenses/by/4.0/), unless explicitly stated otherwise.
* Any other material (including text content, images, the website source code, logos, names and trademarks) are not to be used without the explicit consent of the owner.
* The website is powered by [Netlify](https://www.netlify.com/), [Astro](https://astro.build/) & [GitHub](https://github.com/).
* All of the photography is provided by [Unsplash](https://unsplash.com/collections/9387655/30-seconds-of-code-images).

---

### Snippets Js S Proxy Array Negative Index (`content/snippets/js/s/proxy-array-negative-index.md`)

---
title: Negative index in JavaScript array
shortTitle: Negative index array proxy
language: javascript
tags: [proxy,array]
cover: budapest-palace
excerpt: Ever wanted to use negative indices in JavaScript arrays? Here's a simple way to do it using a Proxy.
listed: true
dateModified: 2022-10-02
---

`Array.prototype.slice()` provides an easy way to **access elements from the end of an array**, using a negative `start` value. While this sounds convenient, the resulting value is an array, so it's necessary to use an index to get an individual element.

This is usually not too bad, but it's interesting to explore other options to understand the language better. In this case, we can use a `Proxy` object to allow **accessing data in an array using negative indexes**. To do so, an appropriate handler needs to be defined for the `get` trap.

The trap's second argument corresponds to the passed index, however it's a string, so it must first be converted to a number using `Number()`. Then, `Array.prototype.length` can be used to calculate the position of the actual element. Finally, `Reflect.get()` can be used to get the value at the specific index, but expects its second argument to be a string.

Putting everything together, this is what this looks like:

```js
const handler = {
  get(target, key, receiver) {
    const index = Number(key);
    const prop = index < 0 ? `${target.length + index}` : key;
    return Reflect.get(target, prop, receiver);
  },
};

const createArray = (...elements) => {
  const arr = [...elements];
  return new Proxy(arr, handler);
};

let arr = createArray('a', 'b', 'c');

arr[-1]; // 'c'
arr[-1]; // 'b'
```

> [!IMPORTANT]
>
> <baseline-support featureId="array-at">
> </baseline-support>
>
> As of May, 2025, `Array.prototype.at()` is **widely available in all modern browsers and Node.js**. You should probably consider using it, if possible, as it provides a more straightforward and performant interface for accessing elements in an array using negative indices without the need for a custom solution.

---

### Snippets Js S Test Driven Development Intro (`content/snippets/js/s/test-driven-development-intro.md`)

---
title: Kickstart a JavaScript project with Test-Driven Development
shortTitle: Test-Driven Development
language: javascript
tags: [webdev,testing]
cover: succulent-5
excerpt: I recently built a project using Test-Driven Development (TDD) and it was a great experience. Here's why I think you should try it, too.
listed: true
dateModified: 2025-03-17
journeyId: js/tdd-library-design
---

I recently decided to try out [Test-Driven Development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development) for a small project. While I've had some experience with the methodology before, I've never tried bootstrapping a project with it from the start.

## What is TDD?

**Test-Driven Development**, often referred to as TDD, is a **software development methodology** that relies on writing tests before writing the actual code. The idea is to define the desired behavior of the code through tests and then write the code to achieve that behavior. This approach transforms the traditional development process, by making testing an integral part of the coding cycle rather than an afterthought.

In short, in TDD, we **test first, code later**. This is in contrast to the traditional approach of coding first and testing later, or not at all.

## Tooling

I decided to use [Vitest](https://vitest.dev/) for this project. It's the tool I've settled on, after trying the likes of Jest and Mocha. It feels faster and, in my experience, it's easier to set up and configure than its counterparts.

> [!TIP]
>
> I didn't really have to configure anything for this project, as I was using native **ES modules** and, later in the development cycle, [Vite](https://vite.dev/), which works great with Vitest out of the box.

As far as **syntax** is concerned, there's nothing particularly special about Vitest. In fact, it's practically the same as every other testing library, using the classic trinity of [`describe`](https://vitest.dev/api/#describe), [`test`](https://vitest.dev/api/#test) (aliased conveniently as `it`), and [`expect`](https://vitest.dev/api/expect.html) for **assertions**.

```js title="spec/myFunction.test.js"
import { describe, it, expect } from 'vitest';
import { myFunction } from '../src/myFunction.js';

describe('myFunction', () => {
  it('should return true when called', () => {
    expect(myFunction()).toBe(true);
  });
});
```

It also comes with a lot of very convenient [matchers](https://vitest.dev/api/expect.html), [mocking](https://vitest.dev/api/mock.html) and pretty much everything you'd expect from a modern testing library.

```js title="spec/fetchUser.test.js"
import { describe, it, expect } from 'vitest';
import { fetchUser } from '../src/fetchUser.js';

describe('fetchUser', () => {
  it('should return a user object', () => {
    const user = fetchUser('john_doe');
    expect(user).toMatchObject({ username: 'john_doe' });
    expect(user.age).toBeGreaterThan(18);
    expect(user).toHaveProperty('email');
    expect(user.name).not.toBeUndefined();
  });
});
```

I especially like the [`test.each()`](https://vitest.dev/api/#test-each) and [`describe.each()`](https://vitest.dev/api/#describe-each) functions, which allow you to **run the same test with different parameters**, sparing you the hassle of repeating the same assertions over and over again.

```js title="spec/isNil.test.js"
import { describe, it, expect } from 'vitest';
import { isNil } from '../src/isNil.js';

describe('isNil', () => {
  it.each(
    [null, undefined]
  )('should return true %s', (val) => {
    expect(isNil(val)).toBe(true)
  });

  it.each(
    [false, '', 0, {}, [], () => {}]
  )('should return false with %s', val => {
    expect(isNil(val)).toBe(false);
  });
});
```

### Custom matchers

One thing I want to mention is how easy it was to [roll up my own **custom matcher**](https://vitest.dev/guide/extending-matchers.html). As my project is centered around regular expressions, I wanted a matcher that would easily check if an expression matches a given string.

Surprisingly, this only took me about **10 lines of code**:

```js title="spec/matchers.js"
import { expect } from 'vitest';

expect.extend({
  toMatchString(regex, string) {
    const { isNot } = this;
    const pass = regex.test(string);
    return {
      pass,
      message: () =>
        `expected ${regex}${isNot ? ' not' : ''} to match "${string}"`,
    };
  },
});
```

The entire process is as simple as calling [`expect.extend()`](https://vitest.dev/api/expect.html#expect-extend) and defining the behavior. To use it in combination with `not`, I only had to check the `isNot` property of the context object. Now I can use it in my tests like this:

```js title="spec/hexDigit.test.js"
import { describe, it, expect } from 'vitest';
import './matchers.js';

import { hexDigit } from '../src/hexDigit.js';

describe('hexDigit', () => {
  it('should match hex digit', () => {
    expect(hexDigit).toMatchString('0');
    expect(hexDigit).toMatchString('9');
    expect(hexDigit).toMatchString('a');
    expect(hexDigit).toMatchString('f');
    expect(hexDigit).not.toMatchString('g');
    expect(hexDigit).not.toMatchString('-');
  });
});
```

> [!NOTE]
>
> There's a caveat to my unbridled enthusiasm for custom matchers and that is I haven't used them all that much just yet. Yet, they seem like a very easy, elegant and hassle-free solution in my **limited experience**.

## Why TDD?

At this point, you might be starting to wonder _why_ I chose TDD for this project. The truth is, I sort of already knew what I was going to make, but I had a few concerns about various aspects of the project and I thought that TDD could help me with them.

### Better API design

I'll dive deeper into this point in a future article, but one of the key concerns I was facing was the **developer experience**. Designing a user-friendly library API is no small task, one I've botched in the past.

By writing tests first, I was able to decide on how I want the library to behave, what methods to expose and what sorts of arguments they should take. I was also able to **focus on using the library before implementing it**. This naturally makes it easier to spot the rough edges early on and make the API easier to use.

```js title="spec/myFunction.test.js"
import { describe, it, expect } from 'vitest';
import { myFunction } from '../src/myFunction.js';

// Wait, what's that second `true` again? Maybe redesign this.
describe('myFunction', () => {
  it('should do something', () => {
    const result = myFunction(10, true, false, true);
    expect(result).toBe('expected result');
  });
});
```

It also puts you in the shoes of the user, instead of the library author – a perspective that can be hard to maintain otherwise. This means that you're **evaluating the tool from the perspective of usability**, instead of the traditional viewpoint of minimizing complexity when writing the code or working around limitations and building clever abstractions.

### Higher confidence

One of the things I noticed as I went through the process was that I was pretty confident about my code. I knew that if I broke something, the tests would catch it. This is a great feeling to have, especially when you're working on a project that you're not entirely sure about or that hasn't materialized fully yet.

```js title="spec/myFunction.test.js"
import { describe, it, expect } from 'vitest';
import { myFunction } from '../src/myFunction.js';

// I'm only done when all these tests pass
describe('myFunction', () => {
  it('should do this', () => {
    // ...
  });

  it('should also do that', () => {
    // ..
  });

  it('should absolutely not do this', () => {
    // ...
  });
});
```

The magic of TDD lies in exactly that. When a function, a module, anything is completed, it lets you know. All relevant tests pass, the behavior is as expected, and you can move on to the next thing. I was especially keen on this aspect, as I could only work on this in small bursts and I wanted to make sure that I was making progress every time I sat down to work on it.

### Easier refactoring

As I'll also explore in a future piece, I refactored the library a handful of times, changing the API in some cases. By **refactoring the tests** to match the result I was looking for, I was always able to know when the refactor was complete and functional. Without tests to back me up, I would have been much more hesitant to make some of these changes.

```js title="spec/myFunction.test.js"
import { describe, it, expect } from 'vitest';
import { myFunction } from '../src/myFunction.js';

// I've refactored this test to how I like the API, now let's fix the code
describe('myFunction', () => {
  it('should do something', () => {
    const result =
      myFunction(10, { test: true, value: false, repeat: true });
    expect(result).toBe('expected result');
  });
});
```

This was especially true when one small change in a file broke the entire library at its core and I wouldn't have realized, because it was a pretty subtle mistake relating to spreading arguments in a function call. Ouch!

## Takeaways

I've been skeptical of TDD for years now, but there's nothing like true hands-on experience to shape one's opinion. Having heard of the benefits before, I can tell you that they're as described on the tin.

_Would I use it again?_ Absolutely. In fact, I think that all my smaller weekend-turned-month projects would benefit from this methodology. It also seems like it forced me down a path of producing code focused on entirely different aspects than I usually do.

_Is it a silver bullet?_ No. I can see how it would be **difficult to implement in larger projects**, especially ones maintained by large teams. I think one of the main shortcomings is that it's hard to retrofit TDD into an existing codebase and, when there are too many moving parts, it can be hard to keep track of everything. But for new projects, especially smaller ones, I think it's a great way to write code.

## Conclusion

In conclusion, I found that TDD was a great way to write code. It helped me design what I wanted the library to do first, outline use-cases and the API, and gave me confidence in my code. It also made refactoring easier, as I could be sure that I hadn't broken anything.

At the end of the day, I give a **solid recommendation for using TDD for smaller projects**, especially when you're just starting out with a new idea or library. It's a great way to write code and it's a great way to make sure that your code is working as expected.

---

### Snippets Js S Tf Idf Inverted Index (`content/snippets/js/s/tf-idf-inverted-index.md`)

---
title: Implementing search in JavaScript, using TF-IDF and an inverted index
shortTitle: TF-IDF and inverted index search
language: javascript
tags: [algorithm,string,regexp]
cover: typewriter
excerpt: Building on top of the Porter stemmer, we'll explore how to use TF-IDF and an inverted index to implement a search algorithm in JavaScript.
listed: true
dateModified: 2025-04-09
journeyId: js/search
---

In the [previous article](/js/s/porter-stemming-algorithm), I went into detail how one could implement the **Porter stemming algorithm** in JavaScript. While the algorithm can be used along with a simple tokenization strategy to search for keywords in a document, it's insufficient for complex searching tasks. In this article, we'll explore how we can use **TF-IDF** for more advanced search tasks and implement an **inverted index** to speed up the search process.

## Tokenization

Before we begin, let's establish a simple **tokenization** strategy. As we're only concerned with plaintext documents, we can use a **regular expression** to split the text into words.

```js
const tokenize = str =>
  str
    .split(/[^a-z0-9\-']+/i)
    .filter(tkn => !tkn.length < 2)
    .map(tkn => tkn.toLowerCase());

const text = 'The quick brown fox jumps over the lazy dog.';
const tokens = tokenize(text);
// ['the', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog']
```

All we're doing here is matching characters other than alphanumeric characters, hyphens, and apostrophes and splitting the text into words. We're also filtering out short words and converting all words to lowercase for easier comparison.

## Stopword removal

**Stopwords** are common words that are often filtered out before or after processing of natural language data. These words are usually not relevant for search tasks and can be safely removed. Lists of stopwords are readily available online, but for simplicity, we'll use a small list of common English stopwords.

```js
const removeStopwords = (stopWords, words) =>
  words.filter(tkn => !stopWords.includes(tkn));

const commonStopWords = [
  'a', 'am', 'an', 'are', 'be', 'been', 'being', 'by', 'cannot', 'could',
  'did', 'do', 'does', 'doing', 'for', 'had', 'has', 'have', 'having', 'he',
  'her', 'hers', 'him','his', 'i', 'if', 'is', 'it', 'its', 'me', 'my', 'no',
  'nor', 'of', 'or', 'other', 'our', 'ours', 'she', 'should', 'so', 'such',
  'that', 'the', 'their', 'theirs', 'them', 'then', 'there', 'these', 'they',
  'those', 'too', 'was', 'we', 'were', 'who', 'whom', 'with', 'would', 'you',
  'your', 'yours'
];

const text = 'The quick brown fox jumps over the lazy dog.';
const tokens = tokenize(text);
const filteredTokens = removeStopwords(commonStopWords, tokens);
// ['quick', 'brown', 'fox', 'jumps', 'over', 'lazy', 'dog']
```

## Arbitrary search

Having a basic understanding of these steps, we can use the Porter stemmer to stem the words and perform a search. We'll use the Porter stemmer from the previous article and search for the word 'jump'.

```js
// Assuming the `porterStemmer` function is available
import { porterStemmer } from './porterStemmer.js';

const documents = [];

const parseDocument = document => {
  const tokens = tokenize(document);
  const filteredTokens = removeStopwords(commonStopWords, tokens);
  const stemmedTokens = filteredTokens.map(porterStemmer);
  return stemmedTokens;
};

const addDocument = document => {
  const terms = parseDocument(document);
  documents.push(terms);
};

const search = query => {
  const stemmedQuery = porterStemmer(query);
  const results = documents.filter(doc => doc.includes(stemmedQuery));
  return results;
};

addDocument(
  'JavaScript is a programming language, used in programming the web.'
);
addDocument(
  'Python is a high-level programming language, used in data science.'
);
addDocument(
  'CSS is a style sheet language, used in web development.'
);
addDocument(
  'HTML is a markup language, used in web development.'
);

search('javascript'); // Returns the first document [0]
search('language'); // Returns all documents [0, 1, 2, 3]
search('programming language'); // Returns nothing []
search('web'); // Returns the first, third, and fourth document [0, 2, 3]
```

As you can see, using this technique has some limitations. While we can extend it to search for **multiple words**, it's overall not particularly good at **ranking the results**. This is why we'll be exploring a better measure of relevance, TF-IDF.

## TF-IDF

TF-IDF refers to the **term frequency-inverse document frequency**. It is a measurement that is intended to reflect **how important a word is to a document in a collection or corpus**. The TF-IDF value increases **proportionally** to the number of times a word appears in the document and is offset by the frequency of the word in the corpus.

The implementation is straightforward. All we need to add is a `Map` to use as a vocabulary and add the terms found in each document to it. Then, we can calculate the **term frequency (TF)** of a term in a document by dividing the number of times the term appears in the document by the total number of terms in the document.

Similarly, we can calculate the **inverse document frequency (IDF)** of a term by dividing the total number of documents by the number of documents that contain it. Finally, we can calculate the **TF-IDF** of a term in a document by multiplying the two values.

> [!NOTE]
>
> I'm using the **IDF formula with smoothing** in these examples, but you can switch it out for a different formula, if you prefer.

We can then search for documents by tokenizing the given query, then calculating the TF-IDF of each term in each document and summing the values. The documents can then be **sorted by the sum of the TF-IDF values** to get the most relevant documents.

```js
const documents = [];
const vocabulary = new Map();

const addDocument = document => {
  const terms = parseDocument(document);
  documents.push(terms);

  terms.forEach(term => {
    vocabulary.set(term, (vocabulary.get(term) || 0) + 1);
  });
};

const calculateTF = (term, documentIndex) => {
  const docTerms = documents[documentIndex];
  const termCount = docTerms.filter((t) => t === term).length;
  return termCount / docTerms.length;
};

const calculateIDF = term => {
  const totalDocs = documents.length;
  const docsWithTerm = vocabulary.get(term) || 0;

  return Math.log((totalDocs + 1) / (docsWithTerm + 1)) + 1;
};

const calculateTFIDF = (term, documentIndex) =>
  calculateTF(term, documentIndex) * calculateIDF(term);

const search = query => {
  const queryTerms = parseDocument(query);

  return documents
    .reduce((scores, doc, index) => {
      const score = queryTerms.reduce(
        (score, term) => (score += calculateTFIDF(term, index)), 0
      );
      scores.push({ document: index, score });

      return scores;
    }, [])
    .filter(result => result.score > 0)
    .sort((a, b) => b.score - a.score);
};

// Supposing the documents are the same as before
search('javascript'); // Returns the first document [1]
search('language'); // Returns all documents [0, 3, 1, 2]
search('programming language'); // Returns all documents [0, 3, 1, 2]
search('web'); // Returns the first, third, and fourth document [0, 3, 2]
```

While subtle, in this example you can see the last query returning the documents in a different order. This is a result of ranking the documents by their TF-IDF values, ranking the documents by relevance.

## Inverted index

The TF-IDF implementation is already a significant improvement over the previous search method. However, it can still be slow for large collections of documents. To speed up the search process, we can use an **inverted index**.

An inverted index is a data structure that maps terms to the documents that contain them. This allows us to quickly find documents that contain a given term. We can use a `Map` to store the inverted index, where **the keys are terms** and the values are maps that map document IDs to term frequencies.

Then, we can alter our `search` function to use the inverted index to calculate the TF-IDF values. This way, we can avoid iterating over all documents for each term in the query, significantly **speeding up the search process**.

```js
const documents = [];
const invertedIndex = new Map();

const addDocument = document => {
  const terms = parseDocument(document);
  documents.push(terms);

  terms.forEach(term => {
    if (!invertedIndex.has(term)) invertedIndex.set(term, new Map());

    const docMap = invertedIndex.get(term);
    docMap.set(documents.length - 1, terms.filter((t) => t === term).length);
  });
};

const search = query => {
  const queryTerms = parseDocument(query);
  const scores = documents.reduce((scores, doc, docId) => {
    const score = queryTerms.reduce((score, term) => {
      if (invertedIndex.has(term)) {
        const indexEntry = invertedIndex.get(term).get(docId);
        if (!indexEntry) return score;

        const tf = indexEntry / doc.length;
        const idf =
          Math.log(
            (documents.length + 1) / (invertedIndex.get(term).size + 1)
          ) + 1;
         return score + tf * idf;
      }
    }, 0);

    if (score > 0) scores.set(docId, score);

    return scores;
  }, new Map());

  const results = Array.from(scores.entries())
    .sort((a, b) => b[1] - a[1])
    .map(([id, score]) => ({ id, score }));

  return results;
};

// Supposing the documents are the same as before
search('javascript'); // Returns the first document [0]
search('language'); // Returns all documents [0, 3, 1, 2]
search('programming language'); // Returns all documents [0, 1, 3, 2]
search('web'); // Returns the first, third, and fourth document [0, 3, 2]
```

## Conclusion

That's all for the time being. We've stitched together all the pieces of a simple, yet powerful and performant search algorithm. It's definitely not as powerful as a full-fledged search engine, but it's a good starting point for understanding how search works under the hood and get into natural language processing (NLP).

---

### Snippets Python S Min Max Element Index (`content/snippets/python/s/min-max-element-index.md`)

---
title: Index of min or max element
language: python
tags: [math,list]
cover: two-cities
excerpt: Find the index of the element with the minimum or maximum value in a list.
listed: false
dateModified: 2024-07-18
---

To find the index of the element with the minimum or maximum value in a list, you can use the `min()` and `max()` functions to get the minimum or maximum value in the list, and then use the `list.index()` method to find the index of that value.

```py
def min_element_index(arr):
  return arr.index(min(arr))

def max_element_index(arr):
  return arr.index(max(arr))

min_element_index([3, 5, 2, 6, 10, 7, 9]) # 2
max_element_index([3, 5, 2, 6, 10, 7, 9]) # 4
```

---

### Snippets Articles Snippet Template (`content/snippets/articles/snippet-template.md`)

---
title: My amazing story
shortTitle: Amazing story
language: javascript
tags: [webdev]
cover: image
excerpt: A short summary of your story up to 140 characters long.
listed: true
dateModified: 2021-06-13
---

Write your story here.

---

### Snippets Css Snippet Template (`content/snippets/css/snippet-template.md`)

---
title: My amazing story
shortTitle: Amazing story
language: css
tags: [layout]
cover: image
excerpt: A short summary of your story up to 140 characters long.
listed: true
dateModified: 2021-06-13
---

Write your story here.

---

### Snippets Git Snippet Template (`content/snippets/git/snippet-template.md`)

---
title: My amazing story
shortTitle: Amazing story
language: git
tags: [branch]
cover: image
excerpt: A short summary of your story up to 140 characters long.
listed: true
dateModified: 2021-06-13
---

Write your story here.

---

### Snippets Html Snippet Template (`content/snippets/html/snippet-template.md`)

---
title: My amazing story
shortTitle: Amazing story
language: html
tags: [link]
cover: image
excerpt: A short summary of your story up to 140 characters long.
listed: true
dateModified: 2021-06-13
---

Write your story here.

---

### Snippets Js Snippet Template (`content/snippets/js/snippet-template.md`)

---
title: My amazing story
shortTitle: Amazing story
language: javascript
tags: [array]
cover: image
excerpt: A short summary of your story up to 140 characters long.
listed: true
dateModified: 2021-06-13
---

Write your story here.

---

### Snippets Python Snippet Template (`content/snippets/python/snippet-template.md`)

---
title: My amazing story
shortTitle: Amazing story
language: python
tags: [list]
cover: image
excerpt: A short summary of your story up to 140 characters long.
listed: true
dateModified: 2021-06-13
---

Write your story here.

---

### Snippets React Snippet Template (`content/snippets/react/snippet-template.md`)

---
title: My amazing story
shortTitle: Amazing story
language: react
tags: [hooks]
cover: image
excerpt: A short summary of your story up to 140 characters long.
listed: true
dateModified: 2021-06-13
---

Write your story here.

---

### Snippets Update Logs Snippet Template (`content/snippets/update-logs/snippet-template.md`)

---
title: My amazing update log
shortTitle: Amazing update log
tags: [updatelog]
cover: image
excerpt: A short summary of your update log up to 140 characters long.
listed: false
dateModified: 2024-12-11
---

Write your update log here.

---

### Snippets Articles S 4 Seo Tips For Developers (`content/snippets/articles/s/4-seo-tips-for-developers.md`)

---
title: 4 SEO tips for developers
shortTitle: SEO tips
tags: [webdev,seo]
cover: sunrise-over-city
excerpt: SEO is a very relevant topics that most developers are inexperienced in. Here are 4 actionable SEO tips you can implement today.
listed: true
dateModified: 2021-06-12
---

SEO is becoming increasingly relevant as the internet keeps growing. While most web developers usually have little experience with SEO, it is not particularly difficult to handle some of the basics. Here are my top SEO tips for web developers:

##  Craft user-friendly URLs and map the appropriately

First and foremost, start at the very beginning, which is your **URL slugs**. For URL slugs the rule is that you want them to be human-readable, with words separated by dashes and with no random letters or digits added. Secondly, it's a good idea to make sure that parts of your URLs match to a **logical structure** in your website (e.g. `/blog/posts/awesome-list-of-seo-tips`). Finally, take the time to build a sitemap and redirect any broken or old URLs to new ones. This will help reduce 404 pages.

## Use structured data to help Google understand your pages

**Structured data** is what Google uses to power its **featured snippets**, those little cards that appear at the top of certain searches. It's a good idea to set up structured data for your pages, which will help you rank higher in search results and possibly even land a featured snippet every once in a while. Just make sure to find the appropriate structured data type and include it in your page and you should be good to go.
## Set up Google Analytics and Google Search Console

This is hardly a new tip, but I think it deserves a spot on the list, as both of these tools are extremely important. **Google Analytics** allows you to track user behavior and gather data that can help you identify problems and opportunities, while **Google Search Console** is helpful for figuring out what users are searching for before landing on your website.

## Keep an eye on your markup, performance and accessibility

Last but not least, something that is probably already on your list, is **optimizing your code**. This includes everything from writing semantic markup and keeping requests to a minimum to optimizing for all device types, making your website accessible and ensuring fast load times. Keep in mind that websites have many moving parts that evolve and change over time, so it's a good idea to audit your website often with a tool like Lighthouse.

---

### Snippets Articles S 7 Chrome Extensions For Web Developers (`content/snippets/articles/s/7-chrome-extensions-for-web-developers.md`)

---
title: 7 essential Chrome extensions for web developers
shortTitle: Essential Chrome extensions
tags: [webdev,devtools]
cover: computer-screens
excerpt: Boost your productivity with 7 must-have Google Chrome developer tool extensions.
listed: false
dateModified: 2021-06-12
---

Google Chrome's developer tools are nothing short of amazing, but there are a few missing tools that can increase your productivity even further. Here are my personal favorite extensions that I use everyday:

## CSS Peeper
[CSS Peeper](https://chrome.google.com/webstore/detail/css-peeper/mbnbehikldjhnfehhnaidhjhoofhpehk?hl=en) is an all-in-one tool for CSS inspection that allows you to quickly peek at the styles, fonts, color palettes and assets of any website you want.

## React Developer Tools
[React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) is the de facto extension when working with React, providing all the necessary inspection tools for your React applications.

## LastPass

[LastPass](https://chrome.google.com/webstore/detail/lastpass-free-password-ma/hdokiejnpimakedhajhdlcegeplioahd?hl=en) keeps your accounts and API keys secure, by providing a free password manager complete with password generation and password-protected notes.

## uBlock Origin

[uBlock Origin](https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm?hl=en) is a simple yet effective ad blocker extension that you can just install and forget about, as it keeps your browsing experience ad-free.

## VisBug

[VisBug](https://chrome.google.com/webstore/detail/visbug/cdockenadnadldjbbgcallicgledbeoc?hl=en) allows you to tweak website designs on the fly, using very intuitive controls and tools, without having to delve into any code at all.

## JSON Viewer

[JSON Viewer](https://chrome.google.com/webstore/detail/json-viewer/gbmdgpbipfallnflgajpaliibnhdgobh?hl=en) provides some much needed syntax highlighting and style customization to JSON responses, which are nowadays very commonplace in most web APIs.

## EditThisCookie

[EditThisCookie](https://chrome.google.com/webstore/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg?hl=en) is a powerful cookie manager extension, that allows you to add, delete, edit and even export and import cookies quickly and efficiently.

---

### Snippets Articles S 8 Tips For Remote Work (`content/snippets/articles/s/8-tips-for-remote-work.md`)

---
title: 8 tips for working from home
tags: [webdev,career,programming,jobs]
cover: kettle-laptop
excerpt: Working from home seems great compared to going to the office, but there are challenges that come with it that you need to overcome.
listed: false
dateModified: 2021-06-12
---

Working from home (also known as remote work) seems like a great alternative to going to the office every morning, but it comes with its own set of challenges that you need to overcome in order to stay healthy and productive. Here are my tips for achieving just that:

## Set up your work environment

Working from home has its perks, but nothing beats a well-designed office space where everything is set up with only one purpose in mind: working. Figure out your home office, experiment with different settings and understand what works best for you as soon as possible. An ideal working space is comfortable, quiet and has the right equipment for you.

## Establish ground rules

Most likely you are not living alone, so you have to establish some ground rules with your roommate or significant other. It's up to you to drive the point home that during working hours you are, for the most part, not home. Sure, you can answer the door if you expect a delivery, but that's pretty much as far as you can go. People should not bother you during working hours, unless absolutely necessary, as small distractions pile up fast.

## Inform others of your availability

It's important to let people know that you are online and working or that you are taking a short break for lunch. Remember that you are still part of a team that requires coordination and others probably depend on your work to some extent. Remember to update your status as necessary to make collaboration easier.

## Socialize with coworkers

Working from home can lead to feelings of loneliness, disconnect, isolation which can quickly spin out of control and lead to depression. Communicate with people on your team as if you were in the same room. A healthy amount of communication will help you feel more like you are all working together rather than each one on their own.

5. Be your best professional self
Nobody might be watching you at home, so you can theoretically slack off as much as you like in your pajamas, but that's not very professional. Try to dress appropriately in case you join a video call and behave professionally, so no inappropriate websites or hours upon hours of checking social media. Ask yourself if someone in a shared office space would do whatever it is you are doing and, if the answer is no, stop doing it.

## Plan your daily and weekly tasks

Having a coherent working plan helps you organize your time and prioritize important tasks above trivial ones. It also helps to put things into perspective and have a general idea of what other people on the team are working on. Plan ahead of time together with your team and keep each other posted on the progress of each task. Short term plans help you get through the day, long term plans help everyone meet their deadlines.

## Use the right collaboration tools

Working from home has its own challenges and issues, so try to find the right tools for the job. Slack, Skype, Zoom, Hangouts are great for communication. Design tools such as Figma or Sketch cloud help you communicate designs quickly and effectively. GitHub is the perfect tool for code collaboration and VS Code has an extension (Live Share) for sharing your code editors. Communicate with your team, figure out your needs and pick tools that work for you.

## Maintain regular hours

It's as easy to forget about breaks as it is to start working late. Try to set a schedule that sits well with your team and stick to it. If you would work 9 to 5 in a regular office environment, do the same at home. Take regular breaks, do not forget to sign off when put in your daily hours and come back tomorrow. After all, it's business as usual.

---

### Snippets Articles S Art Of Debugging Meditation (`content/snippets/articles/s/art-of-debugging-meditation.md`)

---
title: Meditation and the art of debugging
tags: [webdev,programming]
cover: meditation-1
excerpt: What's the connection between meditation and debugging? Let's find out.
listed: true
dateModified: 2025-08-28
---

What is the common characteristic between meditation and debugging? While two seemingly unrelated activities, both require focused attention, patience, and a non-judgmental awareness to observe and resolve what arises. Let's dive deeper into this intriguing connection!

## Debugging as Meditation

Both in meditation and debugging, the key lies in cultivating a state of **awareness and presence**. In order to get the most out of it, one has to **pay close attention** to what's going on. In debugging, this means observing code behavior, while in meditation it means observing thoughts, feelings and reactions.

But the parallel doesn't stop there; in fact, it goes much, much deeper. Both practices require you to **explore the root cause** and underlying patterns. In meditation, this might involve delving into the sources of stress or distraction, while in debugging, it means identifying the root cause of a bug or issue.

A **non-judgmental attitude** that allows for exploration and discovery is also beneficial. Observing thoughts and feelings without attachment equates to analyzing code behavior without jumping to conclusions.

**Patience and persistence** are also key, as both meditation and debugging can involve prolonged periods of **focus and introspection**. It's important to approach each session with a sense of **curiosity and openness**, allowing insights to emerge naturally.

Finally, both bear similarities in their outcomes. Both practices can lead to a **greater sense of clarity, understanding, and resolution**. Apart from fixing an issue, you often attain clarity of thought and a deeper understanding - of code systems or oneself - through the process, fostering professional or personal growth.

## Wu Wei

By now you may be convinced that debugging *does* indeed bear similarities to meditation. In fact, you may even be tempted to approach it in a similar mindset. But *how* does one make the most of this insight?

The answer, in my opinion, might lie in the Taoist concept of **Wu Wei** (无为), meaning "effortless action" or "non-doing." It's about aligning with the natural flow of things rather than forcing outcomes.

When debugging, Wu Wei manifests in a few different ways, that act as useful principles for developers:

### Let go of forcing solutions

Just as in meditation, where one learns to let go of the need to control thoughts, in debugging, it's essential to **release the urge to force a solution**. This means allowing the debugging process to unfold organically, without imposing preconceived notions about what the solution should be.

### Observe without attachment

In meditation, practitioners learn to observe their thoughts and feelings without becoming attached to them. Similarly, in debugging, it's important to **examine the code and its behavior, without labelling it** or yourself as "bad", "flawed", "broken" or "incompetent". Accept the system as it is and don't jump to conclusions. This allows for a more objective analysis of the issue at hand.

### Act in harmony with the system

Wu Wei encourages acting in harmony with the natural flow of the world. In debugging, this means **making changes that feel intuitive and minimal**, intervening only as much as needed. You trust your understanding, make the smallest possible fix, and let the system guide your next steps.

### Embrace pauses and stillness

Sometimes, stepping away or taking a mindful pause leads to insight. Wu Wei emphasizes the power of stillness. Solutions often arise when you're not actively searching for them. In debugging, this might mean **taking a break**, meditating, or simply sitting with the problem for a moment.

### Trust the process

Finally, Wu Wei is about trusting the process and having faith that insights will emerge when the mind is calm and receptive. Allow the subconscious mind to work on the problem. Often, solutions become clear when you least expect them. Trust that **clarity will come**. Progress doesn't always look like constant action.

## Practical tips of meditative debugging

If all of that sounds like hodgepodge to you, here are some **practical tips** that I find helpful and might be more actionable to you:

- **Create a calm environment**: Minimize distractions and create a workspace that promotes focus and tranquility.
- **Practice mindfulness**: Take a few moments to center yourself before diving into debugging. Focus on your breath and clear your mind.
- **Embrace curiosity**: Approach debugging with a sense of curiosity rather than frustration. Ask questions and explore different angles.
- **Take breaks**: Step away from the code when feeling stuck. A short break can provide new insights and perspectives.
- **Reflect on patterns**: After resolving an issue, take time to reflect on the process. What worked? What didn't? This reflection can enhance future debugging efforts.

## Benefits of a Wu Wei-inspired approach

Finally, you might be thinking *what's the value of trying such an approach?*. Sure, you can go your whole life without trying it, but there are some **compelling benefits** to consider:

- **Reduced frustration**: By allowing solutions to emerge naturally, developers can avoid the stress and frustration that often accompany debugging.
- **Greater insight**: Mindful observation and allowing intuition to guide you, you gain a deeper understanding of the problem space.
- **Enhanced focus**: The meditative approach cultivates deep concentration, helping you notice subtle clues and avoid tunnel vision.
- **Improved problem-solving**: Non-attachment frees you from preconceived notions, making you more open to unlikely solutions.
- **Personal growth**: The reflective nature of this approach fosters continuous learning and self-improvement.

## Conclusion

By integrating the principles of meditation and Wu Wei into the debugging process, developers can cultivate a more mindful, creative, and effective approach to problem-solving. Embracing these practices not only enhances individual debugging sessions but also contributes to a healthier, more balanced relationship with code and technology.

---

### Snippets Articles S Avoiding Bad Advice (`content/snippets/articles/s/avoiding-bad-advice.md`)

---
title: Beware of Bad Programming Advice - Spotting Senior Juniorism
shortTitle: Avoiding Bad Programming Advice
tags: [webdev,career,programming]
cover: inside-weather
excerpt: Don't fall victim to bad advice! Learn to identify "senior juniorism", and develop critical thinking to make better coding decisions.
listed: true
dateModified: 2023-06-04
---

Aspiring programmers are always looking for advice, especially from experienced developers who have walked the same path. However, not all advice is created equal. In fact, some advice can be downright dangerous, leading to poor coding practices and bad habits. This is where the phenomenon of **"senior juniorism"** comes in – where juniors write too much about the few things they know, completely oblivious to their own blind spots.

## How we got here

If I had to guess, I'd blame the _fake it till you make it_ mentality first and foremost for this situation. While this phrase can be motivating, it's often taken too seriously, leading to bad advice and misinformation spreading like wildfire. When someone pretends to be an expert, they may inadvertently give advice that is outdated, incomplete or simply incorrect.

Another culprit is the fact that many developers are **self-taught**. The internet has given us access to a wealth of information, but it's also made it easier than ever to **skip over the basics**. This can be especially dangerous when it comes to security, where a single mistake can lead to a major breach.

Last but not least, **lack of experience** can also be a factor. While it's true that experience is not everything, it's more likely that you'll be exposed to different situations, problems and opinions over time, which will help you form a more **balanced view** of things. This sort of exposure is something that you simply don't have when you're just starting out.

Combining all of this with the _learning in public_ mentality that is often pushed to new developers and things can easily get out of hand. Thus, it's imperative that we learn to spot bad advice.

## Spotting bad advice

To avoid falling for bad advice, it's important to understand the **three types of knowledge**: what you know you know, what you know you don't know, and what you don't know you don't know. It's important to acknowledge that there are many things we don't know, and be humble enough to admit it. Even the most experienced developers have gaps in their knowledge.

So how can you avoid falling for bad advice? One way is to pick **reputable sources**, such as books, articles, or experts in the field. However, even reputable sources can be **biased or outdated**. That's why it's important to **do your own research**, experiment with code, and develop your **critical thinking skills**.

## Some self-reflection

As my writing (and this very post) is a source of advice in itself, I suggest you take it with a grain of salt, too. I'm not immune to this phenomenon and I've made many mistakes in the past, hopefully without causing too much harm. I've also followed and reproduced bad advice at times, privately or publicly. Here's a few examples of bad advice I've fallen for:

- **Coercion is always bad**, except it isn't. It's a language feature that can be useful in certain situations and any confusion around it stems from not fully understanding how it works.
- **Never use classes in JS**, because they're syntactic sugar on top of prototypes. This might be true, but to a beginner coming from an object-oriented background, it feels a bit like gatekeeping.
- **Avoid useEffect in React**, opting for third-party hooks instead. Those hooks are certainly useful, but they use `useEffect` under the hood, so you'll eventually need to learn about it one way or the other.

I've definitely made my fair share of mistakes, and I'm sure I'll make many more in the future. However, I'm always trying to learn from them and improve my writing. I'm also trying to be **more mindful of the advice I give**, and I hope you'll do the same if you're sharing your knowledge with others.

## Conclusion

In conclusion, senior juniorism can be a dangerous phenomenon that can lead to bad advice spreading To avoid falling into this trap, it's important to acknowledge the limits of your knowledge, pick reputable sources, do your own research, and develop your critical thinking skills. Remember, not all advice is created equal, and it's up to you to make informed decisions based on the specific context.

---

### Snippets Articles S Bash Alias Dollar Sign (`content/snippets/articles/s/bash-alias-dollar-sign.md`)

---
title: A bash alias for copying commands from the web
shortTitle: Bash alias for copying from the web
language: git
tags: [configuration]
cover: capsule-coffee
excerpt: Learn how to eliminate the annoyance of copying dollar signs ($) along with terminal commands from the web with a simple bash alias.
listed: true
dateModified: 2023-03-05
---

If you've ever copied a terminal command from the web, chances are you've also copied the **dollar sign** (`$`) that precedes it. This is a small annoyance that many developers encounter almost daily, but it can be easily dealt with. All you need to do is add an `alias` for the dollar sign to your `.bashrc` or `.zshrc` file:

```shell
alias '$'=''
```

But what about variables? Aren't these prefixed with a dollar sign? This bash alias won't break your scripts, as aliases replace only the first word of each simple command. Simply put, the only dollar sign that will be replaced is the one at the **beginning of the line**.

---

### Snippets Articles S Benefits Of Writing (`content/snippets/articles/s/benefits-of-writing.md`)

---
title: The benefits of writing
tags: [webdev,career,programming,jobs]
cover: laptop-view
excerpt: Writing about a topic can often sound boring or worthless. But there are significant benefits to doing it from time to time.
listed: true
dateModified: 2022-11-05
---

I was recently tasked with writing a design document at work. What first seemed like a tiresome chore I had to endure turned out to be both enlightening and beneficial both to me and my team. This isn't just a case of making the best of a bad situation, but rather a case of rediscovering the reason behind why writing down your thoughts isn't an exercise in futility. Instead of boring you with a long-winded story about the task, I'll just present the takeaways and hopefully do some storytelling in the process. So why should you write then?

## You learn more about the topic at hand

Presenting a topic requires meticulous **research** in order for you to be able to write down the most interesting and relevant findings. This process will uncover any **gaps in your knowledge** of said topic, gaps your research will in turn have to cover. I've seen this numerous times in the past while doing research both for this website as well as tasks at work and it always holds true. If nothing else, you will gain a **deeper understanding** of whatever it is you are presenting, which will be useful sometime in the future. In the case of my task, I understood more about the business logic, how premature optimizations got in the way of stability and why it was so hard to consistently reach the desired state, as I discovered a hidden layer of complexity I didn't know about before.

## You discover the root of the problem

Most of the time we assume to know the problem well enough to try and solve it. A lot of the time, I've found out, I know a lot less than I originally thought about the domain, the problem and, more importantly, the root cause of it. Again, **presenting the problem to a reader** will put you through the process of having to **explain it** and boil it down to the key details. This more often than not results in a concise description of whatever is at the **root of the problem**. Working on my design document, I figured out that the root of the problem was essentially a set of bad assumptions that kept piling up and had some mismatches between the backend and frontend code.

## You understand past decisions

This one applies to problems you aren't the first one to tackle, but might still be relevant in other situations. As you go you will come across whatever **implementation details** you didn't know about. If you take a closer look, you'll figure out how those came to be in the first place and **why those decisions were made** in the past. Understanding the reasons behind past decisions is crucial in figuring out if the original set of needs, restrictions and assumptions is still valid and how to amend them if necessary. As I was already knee-deep in this task, I realized that the decisions we made almost one year prior were valid at the time, decisions I didn't fully understand back then. A couple of them, however, didn't seem to stand the test of time as business needs changed and we'd have to update them accordingly.

## You come up with more varied solutions

Understanding past decisions, covering the gaps in your knowledge and boiling down the problem to a concise description you should already be a lot more informed about the topic you are researching than before. Hopefully at this stage, you will have an itch to try **a different solution** from the one you originally envisioned or you'll have come up with a solution where there was none. More varied solutions are always better because you can **compare tradeoffs** and get a better understanding of what's best. In my case, I ended up considering a solution I had mostly dismissed originally. And it was that solution which made the most sense to me in the end, even though it was more radical than the one I was planning to propose originally.

As a closing thought, try writing even if nobody is going to read it. Write it for you. It will help you understand, explain, reason about and tackle the task all at once. It's not a waste of time, it's a powerful tool you could leverage from time to time.

---

### Snippets Articles S Code Modularization (`content/snippets/articles/s/code-modularization.md`)

---
title: You Shouldn't Always Modularize Your Code
shortTitle: Case Against Modularization
tags: [webdev,career,programming]
cover: salad-1
excerpt: Have you ever stopped to wonder if modularization is right for your use case? What if I told you it might not be?
listed: true
dateModified: 2025-05-10
---

Modularization is often hailed as a best practice in software development. It promises cleaner codebases, easier maintenance, and better scalability. However, **modularization is not a one-size-fits-all solution**. While it shines in large-scale projects with complex requirements, it can become a burden in smaller projects or early-stage development. In this article, we'll explore why you shouldn't always modularize your code and when sticking to a monolithic approach might be the better choice.

## When Modularization Works Best

Modularization is undeniably powerful in the right context. For **large projects**, where individual modules are already substantial and require **independent maintenance and testing**, modularization can help manage complexity. Breaking down a massive codebase into smaller, self-contained modules makes it easier to:

- Assign ownership to different teams.
- Test and deploy modules independently.
- Scale the application by focusing on specific areas.

For example, in a large e-commerce platform, separating the payment gateway, product catalog, and user authentication into distinct modules makes sense. Each module can evolve independently, and teams can work in parallel without stepping on each other's toes.

```js
// Example: Modularized payment gateway

// filepath: /modules/payment/index.js
export const processPayment = (amount, method) => {
  // Payment processing logic
  console.log(`Processing ${amount} via ${method}`);
}

// filepath: /modules/payment/validation.js
export const validatePaymentDetails = (details) => {
  // Validation logic
  return details.cardNumber && details.expiryDate;
}
```

However, **not every project is a massive e-commerce platform**. For smaller projects, modularization can introduce unnecessary complexity.

## The Pitfalls of Premature Modularization

**Early and aggressive modularization is often a sign of premature optimization.** Developers may feel compelled to break their code into modules from the start, thinking it will save time later. In reality, this can lead to:

1. **Increased complexity:** Managing multiple modules, even for a small project, can be overkill. You'll spend more time setting up and maintaining the structure than writing actual code.
2. **Testing nightmares:** Interdependent modules can make testing a headache. Changes in one module might break another, leading to cascading issues.
3. **Ecosystem bureaucracy:** Rolling out releases for multiple modules, tracking changes, and managing dependencies can eat into your productivity.

For example, imagine a simple blog application. Splitting it into separate modules for posts, comments, and users might seem like a good idea. But for a small team or solo developer, this adds unnecessary overhead.

```js
// Monolithic approach for a small blog app

const createPost = (title, content) => {
  console.log(`Post created: ${title}`);
}

const addComment = (postId, comment) => {
  console.log(`Comment added to post ${postId}: ${comment}`);
}

const registerUser = (username) => {
  console.log(`User registered: ${username}`);
}

// All logic in one place, easy to manage for a small project
```

## Debugging and Security Challenges

One of the biggest downsides of modularization is the **cascading effect of changes**. Debugging an issue in an external module can lead to a chain reaction of updates, releases, and deployments across multiple repositories. This is especially frustrating when dealing with **security vulnerabilities**. While modularization can make dependency updates easier, it also means you have to track and patch vulnerabilities across all modules.

For example, if a critical bug is found in a shared utility module, you'll need to:

1. Fix the bug in the utility module.
2. Release a new version of the module.
3. Update all dependent modules to use the new version.
4. Test everything to ensure nothing breaks.

This process can be time-consuming and error-prone, especially for small teams.

## Monorepos: A Partial Solution

Monorepos are often touted as a way to mitigate the challenges of modularization. By keeping all modules in a single repository, you avoid the hassle of managing multiple repositories. However, **monorepos don't eliminate the underlying problems of modularized code**. You still have to deal with interdependencies, testing, and cascading changes.

For smaller projects, monorepos might simplify some workflows, but they don't justify modularization if the project doesn't need it in the first place.

## Why Monoliths Make Sense for Smaller Projects

For **smaller projects, side projects, or tiny teams**, sticking to a monolithic architecture is often the better choice. A monolith keeps everything in one place, making it easier to:

- Understand the codebase.
- Debug issues without worrying about inter-module dependencies.
- Deploy the entire application as a single unit.

If modularization becomes necessary later, it will happen naturally. For example, if a piece of code becomes reusable across multiple projects, you can extract it into a module when the need arises.

```js
// Example: Extracting reusable code later

// filepath: /utils/formatDate.js
const formatDate = (date) => {
  return new Date(date).toLocaleDateString();
}

// filepath: /app.js
import { formatDate } from './utils/formatDate.js';

console.log(formatDate('2025-04-19')); // Reusable utility
```

This approach allows you to focus on building the application first, without getting bogged down by unnecessary structure.

## Conclusion

**Modularization is a tool, not a rule.** While it's invaluable for large-scale projects, it can be a burden for smaller ones. Premature modularization often leads to increased complexity, testing nightmares, and productivity loss. For smaller projects, monoliths are usually the better choice, offering simplicity and ease of management.

Remember, **you can always modularize later if the need arises.** Start simple, and let your project's requirements guide your architecture. By avoiding unnecessary modularization, you'll save time, reduce frustration, and focus on what really matters: building great software.

---

### Snippets Articles S Design Systems Reality (`content/snippets/articles/s/design-systems-reality.md`)

---
title: Design systems rarely survive contact with the real world
shortTitle: Design systems
tags: [webdev]
cover: tranquil-riverscape
excerpt: Design systems are often seen as the holy grail of design and development, but there are a few issues that can arise in the real world.
listed: true
dateModified: 2025-06-14
---

Over the years, both as a professional and as a hobbyist, I've used, maintained and developed a number of **design systems**. While the promise of a unified, reusable set of components is alluring, my personal experience has shown that design systems rarely survive contact with the real world. Instead of being a panacea, they often become a source of friction, slowing teams down and introducing new challenges that are rarely discussed outside of closed-door retrospectives.

> [!IMPORTANT]
>
> **This is an opinion piece**. The views expressed here are my own and may not reflect those of the organizations I work for or with. As opinions often tend to be **subjective**, I encourage you to take them with a grain of salt.

## What design systems are for

The promise of design systems is simple: create a **shared language** and toolkit that designers and developers can use to **build consistent, scalable products**. In ideal scenarios, design systems provide clear benefits: **faster onboarding, fewer inconsistencies**, and a foundation for **rapid iteration**. When done right, they can be a powerful force multiplier for teams.

## The reality of design systems

Yet, the **real world** is full of use cases that are **messy, complex, and unpredictable**. The gap between theory and practice quickly becomes apparent. You simply **can't design for every scenario**, and the importance of flexibility and adaptability is often underestimated. Design systems tend to be **rigid and verbose**, making them hard to use in practice. They're **almost never complete**, especially in projects that evolve rapidly and dynamically.

## Escaping isolation

A problem I've noticed is that design systems are often **created in isolation**, as an attempt to organize, or worse yet reorganize, a messy project. This is especially problematic when replacing existing designs and code, since many **edge cases are unknowable** to the design team.

Moreover, limited capacity during replacement can lead to **half-baked solutions**, or systems applied only to parts of the codebase. Supporting **multiple platforms and environments** introduces further complications, as do the idiosyncrasies of the codebases or platforms in question.

Organization-wide **alignment** is also an uphill struggle. People resist change, and any shift in the team can affect the vision and direction of the design system, leading to inconsistencies and confusion.

## Development is hard

Nobody is surprised to hear that **software development is inherently complex**, and good, organized design is only one facet of the process. This leads to a situation where tools and solutions are often bent out of shape out of necessity, not incompetence or negligence.

Cracks often first appear in convention **discrepancies** (e.g. component names) between design and development, then propagate into implementation details. Developers, faced with a tangled web of issues, will often **resort to hacks** that pile up over time. Designers aren't immune either; they may abuse components to make their work easier or find **shortcuts**. The underlying code often doesn't match the design system, and **external component libraries** can slow down development, requiring specialized changes to adapt to new constraints.

## Time is the enemy

Small changes tend to accumulate with time, resulting in **deviations** that are hard to track. **Patches and edge case solutions** in the code may or may not be communicated to the design team, but don't always make their way back into the designs. A chat in the hallway or a quick Slack message is bound to be forgotten when there's a deadline looming.

Over time, **deterioration** and a proliferation of edge cases can make even the best design systems unwieldy. As changes pile up, so does **technical debt**, which in turn leads to more hacks and workarounds. The design system becomes a burden rather than a tool, and the original vision is lost in the noise.

## Productivity & velocity

Product teams are under constant **pressure to deliver**, and contributing back to the design system is rarely a priority. **On the fly decisions** may never be added to the system, but get replicated throughout the codebase. Alignment with the design system can bog down the **fast iterations** that product management demands, and the system itself may be seen as a hindrance to productivity rather than a tool to enhance it.

A similar situation arises for **hobby projects**, open source and small teams. The initial excitement of creating a design system can quickly fade when faced with the reality of **maintaining it**. The time and effort required to keep it up to date can be **overwhelming**, especially when the focus is on delivering features and fixing bugs, or when time to work on a side project is limited due to other commitments.

## Conclusion

Design systems are a **great idea in theory**, but they often fail to deliver the expected results in practice. It's better to **focus on the principles** of good design and development, rather than enforcing a rigid system — especially in fast-paced environments with constantly evolving codebases.

**Good communication is key to success** in larger teams, while for small teams or solo projects, a design system may end up being overkill. Many of these issues also apply to code architecture and design documents, and the solutions are often similar: **communication, flexibility, and adaptability**.

Still, there's always room for improvement: a design system **can be a valuable tool** if adopted slowly, iteratively, and with the **right mindset**. Ultimately, they require careful consideration and a willingness to adapt to the realities of software development, both from a design and a development perspective.

---

### Snippets Articles S Dns Record Basics (`content/snippets/articles/s/dns-record-basics.md`)

---
title: A beginner's guide to DNS records
shortTitle: DNS Record Basics
tags: [webdev,dns,server,cheatsheet]
cover: sparkles
excerpt: DNS records may not be something you work with frequently, but having a basic understanding can be highly beneficial.
listed: true
dateModified: 2022-01-09
---

Most web developers go about their day-to-day without having to deal with DNS records most of the time. Regardless, knowing what DNS stands for and the types of DNS records are pretty useful.

## DNS Definition

The Domain Name System (abbreviated to DNS), translates human-readable domain names (e.g www.google.com) to machine-readable IP addresses (e.g. 142.250.186.46).

## DNS Records

A DNS is made up of multiple records of different types, each one with its own purpose. Here's a breakdown of the most commonly-used ones:

- **A record**: The address record. Used to map a domain name to an IPv4 address. Similarly, the **AAAA record** is used to map a domain name to an IPv6 address.
- **CNAME records**: A canonical name record. Creates an alias that points to another domain or subdomain, but never an IP address.
- **ANAME record**: Allows you to point the root of your domain to a hostname or a domain name.
- **TXT records**: Allow the addition of limited text notes and is often used for ownership verification purposes, validation or security.
- **MX record**: Specifies the mail server responsible for accepting the incoming and outgoing emails for a domain. Should point to a mail server name, not an IP address.

---

### Snippets Articles S Escaping Tutorial Hell (`content/snippets/articles/s/escaping-tutorial-hell.md`)

---
title: Escaping tutorial hell
tags: [webdev,career,programming,jobs]
cover: campfire
excerpt: Many beginners get stuck in tutorial hell without even realizing. Here are 4 simple ways to escape tutorial hell and get back to learning.
listed: true
dateModified: 2022-05-26
---

Tutorial hell refers to the stage in your learning journey, where you are following along with tutorials, not truly learning that much. Tutorials are by nature a curated experience. While that's not definitively bad, it's **not always the best way** to explore the landscape and familiarize with concepts and tools. Neither is it the best way to learn how to think for yourself. So how can you escape it?

## Debug

The most common exit ramp comes up when you end up in debugging mode. Oftentimes a tutorial can be outdated or something in the setup can be just a tiny bit different. At this point, you have to figure out how to solve the issue to proceed.

Entering debugging mode, you realize that **you know only a part of the big picture**. What you don't know might not be immediately obvious, but with perseverance you will be able to figure it out. At that point, you can start looking for answers to solve the issue.

Debugging a problem offers a little bit of **exposure to the underlying technologies and concepts**. This way you can dip your toe into whatever it is you are working with, without getting overwhelmed.

## Be curious

Curiosity killed the cat, but not you. Stop every step of the way to ask what it is you are learning. Do you understand the concepts? Do you know what it is you are building? Is this tool your only option? What problem does it solve? Are there others?

Questions are a valuable tool to help you explore the landscape. You don't necessarily need to answer them all, nor do you need to read tons of documentation or articles about a topic. Simply **taking a peek at the big picture** and **asking questions** is enough. You can always come back later and dive deeper.

## Experiment

The third and best way to escape tutorial hell is to experiment. After you finish a couple of tutorials, **put your skills to the test**. Try to build something from scratch, looking up anything you need to, as you go. It won't be as easy or as fast as the tutorial was, but you'll learn a lot more.

After you build a project, **take another look**. Figure out what you could have done differently or what parts of it you don't understand well enough. Refine it, polish up parts of it, add new features. The more time you put into it, the better you will become.

Then do it a few more times and experiment with a few more ideas. As soon as you start working on your own projects and get into a rhythm, it's a lot easier to learn new things. Some slopes will be steeper and others will be more gentle. But you will feel much **more confident in your skills** than blindly following tutorials.

## Learn your own way

As a closing suggestion, I want to urge you to **find your own learning style**. Tutorials might be right for some and I don't suggest you dismiss them entirely. After all, we all have to start somewhere. But if they don't work for you, **mix it up a bit**. Try reading the documentation, solving coding exercises or tinkering with an existing project. Everyone is different and there's no one size fits all when it comes to learning.

For some, much like myself, teaching is the best learning experience. After all, if you can't explain something simply, you probably don't understand it well enough. Explaining, then, can lead you to researching, debugging, experimenting and being curious all at once. And teaching, much like learning, can be done in many many different ways.

---

### Snippets Articles S Explicit Code Is Always Better (`content/snippets/articles/s/explicit-code-is-always-better.md`)

---
title: Explicit code is always better
language: javascript
tags: [webdev]
cover: laptop-notes-2
excerpt: A thesis on why magic abstractions are harmful to codebases and teams.
listed: true
dateModified: 2025-11-04
---

I recently had the misfortune of working with a little-known JavaScript library and boy did I hate it! From naming conventions that are hard to follow to abstractions that feel like black magic, in my mind it's exactly what's wrong with overengineered tools and a justification for a move back to explicit, albeit potentially verbose and boilerplate-heavy, code.

I know what you may be thinking but hear me out: explicit code _is_ always better. It beats magic. It makes intent obvious. It reduces time spent chasing down nasty bugs.

## Readable by design

Implicit behavior can feel clever. It can also be a silent **source of mistakes**. When a framework maps files to routes or renames properties automatically, you must remember the transformation. You must trace it across files and sometimes across languages.

Explicit code shows the path. An explicit import, export, or variable name tells the reader where a value comes from and how it is used. That **clarity** is worth a few extra lines.

```js
// Hard to follow
const result = data.
  map(item => item.value).
  filter(v => v > 10).
  reduce((a, b) => a + b, 0);

// Easier to follow
const values = data.map(item => item.value);
const filteredValues = values.filter(v => v > 10);
const result = filteredValues.reduce((a, b) => a + b, 0);
```

When writing code with **maintainability** in mind, prefer a named intermediate variable to a long chain of anonymous transforms. The name becomes a breadcrumb for future readers. Remember that clever one-liners can be elegant. But, they can also be cryptic. Favor **explicit, readable implementations over terseness** when clarity matters. It also makes the code **easier to test and reuse**.

@[You may also like](/js/functional-programming)

## Types for clarity

I've recently dabbled in Go and I was surprised how intuitive I found its **type system** and how much use I got out of it. While I used to dislike the rigidity of static types, I have now come to appreciate their value in making code more **predictable** and easier to maintain.

**Static typing documents intent**. It signals which fields are required and which are optional. It narrows the space of valid values and makes refactors safer.

```ts
type User = {
  id: string;
  name: string;
  email?: string;
};

function findUser(id: string): User | null {
  // implementation
  return null;
}
```

Sure, I'm not a fan of TypeScript, but my gripes with it are entirely separate from the value of types in general. **Types make a contract explicit**. You do not have to search for implicit assumptions across modules.

@[Further reading](/js/s/complete-guide-to-js-type-checking)

## Traceable imports and exports

We often despise adding all the wiring, boilerplate and ceremony. Sure, it's **verbose** and it can feel mind-numbingly boring. But, those lines are **discovery points** for anyone who reads the code later, including our future selves.

Go forces **explicit imports** and **simple module boundaries**. That constraint is, oddly enough, an advantage. It makes dependencies obvious. It lowers the barrier to entry for new contributors.

```js
// Explicit imports
import express from 'express';
import userRoutes from './routes/user.js';

const app = express();
app.use('/users', userRoutes);

// Explicit exports
export default app;
```

Explicit wiring creates followable links. You can **trace imports, types, and function calls** back to the source. That makes it easier to find where a value is created or transformed.

You'll eventually have to trace back through the code to find _something_. And, when you do, you'll be grateful that you did the work upfront, spending those minutes that felt like a waste to write something more explicitly.

## Conventions increase cognitive load

**Conventions** can be useful. They cut boilerplate, but they also hide behavior. When naming conventions travel from the server to client code, or when build tools transform property names, those mappings become **invisible mental work**.

**Context switching between languages** already costs attention. Adding implicit transforms multiplies that cost. Choose **explicit transformations** when you can. If you must sprinkle some magic, **document** it and **add tests** that show the mapping.

```js
import { deepCamelizeKeys } from './deepCamelizeKeys.js';
// server response: { user_id: 1, user_name: 'Alice' }

// Implicit transformation, via convention
const user = fetch('/api/user')
  .then(res => res.json())
  .then(deepCamelizeKeys);

// Explicit transformation, key by key
const user = fetch('/api/user')
  .then(res => res.json())
  .then(data => ({
    userId: data.user_id,
    userName: data.user_name,
  }));
```

Every hidden rule increases the **mental model** a developer must hold. Projects with many implicit rules slow down **onboarding**. They make **debugging** and **refactoring** riskier. They also make **code reviews** harder.

**Make explicit code the default** and only reach for conventions when they provide a clear benefit. Ensure they are few, well documented, and widely understood.

> [!TIP]
>
> As a rule of thumb, if a convention feels like magic and obscures behavior, avoid it. The short term gain rarely justifies long term cost.

## Abstractions as barriers to entry

**Abstractions remove repetition**. They also **hide details**. As a library grows, the abstraction surface usually expands. That expansion increases the **cost of learning and debugging**.

Junior developers and newcomers face the steepest penalty. Do not reject boilerplate reflexively. If repeated, explicit code becomes a **productivity** sink, build a small, well documented abstraction.

```js
// Implicit state management, hidden state
const countert = (initial = 0) => {
  let value = initial;
  return {
    increment: () => { value += 1; },
    decrement: () => { value -= 1; }
  };
}

// Explicit state management, more visible state
class Counter {
  constructor(initial = 0) {
    this.value = initial;
  }

  increment() {
    this.value += 1;
  }

  decrement() {
    this.value -= 1;
  }
}
```

Explicit languages, like Go, tend to encourage **careful abstraction**. Teams only reach for abstraction when the repetition justifies it. Reach for an abstraction when it provides a clear benefit. **Keep abstractions small**. Document their invariants. Add tests and provide examples. Keep an **escape hatch** so developers can bypass the abstraction when necessary.

## Practical rules to apply today

- **Prefer explicit exports and imports** in your code to make it easier to trace dependencies and follow data flow.
- **Use static types**, if possible, or at least **add type checks** whenever they make sense to help clarify intent and catch mistakes.
- **Avoid cross language implicit transformations** that add cognitive load, unless they are few, truly necessary, and thoroughly documented.
- **Prefer readable functions and named variables** over clever one-liners and chains of anonymous transforms.
- **Document** any convention that behaves like magic and **add tests** showing the mapping to reduce the barrier to entry.

## Closing thoughts

I hope I have at least changed your mind about the value of explicit code. It may feel like a chore to write, but it pays off in the long run. Make clarity your default. Start explicit. Add convenience when it is necessary and safe. When you do, document and test the shortcut so future readers do not have to guess.

---

### Snippets Articles S How 30 Seconds Of Code Is Deployed (`content/snippets/articles/s/how-30-seconds-of-code-is-deployed.md`)

---
title: How 30 seconds of code is deployed
tags: [webdev]
cover: home-office-3
excerpt: A historical run-through of how 30 seconds of code was deployed then and now.
listed: true
dateModified: 2025-07-03
---

<style>
  h2 > small {
    display: block;
    color: var(--color-text-lighter);
    font-size: var(--font-md);
    font-weight: var(--font-weight-normal);
    margin-top: var(--spacing-4);
  }

  details > p + p {
    margin-block-start: var(--layout-row-spacing)
  }
</style>

This is perhaps the longest article I've written, but I feel there's a ton of value to be had from my insights, mistakes, and the evolution of how 30 seconds of code has been deployed over the years. Grab a cup of coffee, and find a quiet spot, as we take a trip down memory lane.

> [!TIP]
>
> To view the state of the repository at a given time in history, you can visit the **Commit Reference** and **Browse files**.

<h2>
  <a id="markdown-only" href="#markdown-only">Markdown-only</a>
  <small>Early December 2017</small>
</h2>

<figure>



<figcaption>
  Output of the initial tooling written for 30 seconds of code
</figcaption>
</figure>

**Tools:** Node.js · [Commit reference](https://github.com/30-seconds/30-seconds-of-code/commit/bf855e55e228991e864adc245504099df8342699)

The original build process involved around the generation of Markdown with all of the snippets ordered and organized as a **Markdown file** with internal links presented as a page of contents.

Some [static Markdown parts](https://github.com/30-seconds/30-seconds-of-code/tree/bf855e55e228991e864adc245504099df8342699/static-parts) were used for the file's header and footer and a [builder script](https://github.com/30-seconds/30-seconds-of-code/blob/bf855e55e228991e864adc245504099df8342699/scripts/builder.js) would read each file in order and add its contents to an output string. The final result would be **written to the repository's README**.

Additionally a [linter script](https://github.com/30-seconds/30-seconds-of-code/blob/bf855e55e228991e864adc245504099df8342699/scripts/lintSnippet.js) was also provided to apply some **predefined rules** ([Semistandard](https://github.com/standard/semistandard)) to the code in the Markdown files.

As far as automation went, it was completely non-existent, needing **manual script runs and commits for each build**.

<h2>
  <a id="earliest-website-attempts" href="#earliest-website-attempts">Earliest website attempts</a>
  <small>Late December 2017</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Dec. 31st, 2017
</figcaption>
</figure>

**Tools:** Node.js, HTML, Travis CI, GitHub Pages · [Commit reference](https://github.com/30-seconds/30-seconds-of-code/commit/ed1170168357a9716751c596816040e7b751deeb)

As time went by, the [30 seconds of code bot account](https://github.com/30secondsofcode) (_not a real bot_) was introduced. The account was used for automation tasks, such as **automated builds** via the creation of a **GitHub Token** that was then used to push commits to the repository. The following `git push` command was what allowed the bot to push new code to the repository:

```shell
git push --force --quiet "https://${GH_TOKEN}@github.com/Chalarangelo/30-seconds-of-code.git" master > /dev/null 2>&1
```

Using the token, stored in an **environment variable** on GitHub, as part of a special URL one can push code from the token authorizer's account to the repository (provided the author has sufficient access). The rest of the command is written in such a way to **prevent outputting the token into the logs** and exposing a backdoor that anyone could exploit.

<figure>



<figcaption>
  Current GitHub UI for storing environment variables as part of GitHub Actions (old UI was not much different)
</figcaption>
</figure>

For the purpose of **automation**, [Travis CI](https://www.travis-ci.com/) was used. This allowed changes pushed to master to trigger new builds, as well as other automation tasks. The [configuration file for Travis](https://github.com/30-seconds/30-seconds-of-code/blob/ed1170168357a9716751c596816040e7b751deeb/.travis.yml) specified which tasks to run (such as linting via Semistandard and [Prettier](https://prettier.io/)) and scripts to execute.

**Tagging** was added during that time in the form of a [plaintext file](https://github.com/30-seconds/30-seconds-of-code/blob/ed1170168357a9716751c596816040e7b751deeb/tag_database), specifying a tag for each individual snippet. A [tagging script](https://github.com/30-seconds/30-seconds-of-code/blob/ed1170168357a9716751c596816040e7b751deeb/scripts/tag.js) was added as well, allowing new snippets to be added to the file as *uncategorized*, which then needed manual processing.

<figure>



<figcaption>
  Approximation of the GitHub Pages configuration in the current GitHub UI
</figcaption>
</figure>

Alongside the previous build script, a new [web script](https://github.com/30-seconds/30-seconds-of-code/blob/ed1170168357a9716751c596816040e7b751deeb/scripts/web.js) was added. It followed a very similar pattern, generating an **HTML page**, clearly inspired by [Lodash's website](https://lodash.com/docs/4.17.15). The output of the web script was then stored in a directory and **deployed automatically** on [GitHub Pages](https://pages.github.com/).

<figure>



<figcaption>
  Namecheap's current UI with some information about the website domain
</figcaption>
</figure>

The current domain was obtained during this time from [Namecheap](https://www.namecheap.com/), but unfortunately the configuration that was used back then is lost to time.

<details>
<summary> <h3>Trivia</h3> </summary>

Travis CI was misconfigured for a little while, resulting in Travis builds triggering new builds as soon as the pushed to master. This caused an **infinite loop** that burned through the entirety of the free credits the project was provided with. Luckily, the Travis team was kind enough to restore them after I messaged them.

GitHub Issues malfunctioned at some point, resulting in the issue counter jumping a few numbers (something like 100). To this day, no incident report has been published and GitHub has not explained what happened back then.

The bot **token** was originally **incorrectly added in plaintext** as part of the bash script where it's used. This resulted in a **security vulnerability** that was identified by someone in a chatroom. Luckily, no malicious actors took advantage of it and I generated a new token after the code was fixed. If I remember correctly, a full rewrite of the `master` branch was performed to purge the offending commits.

</details>


<h2>
  <a id="multi-page-website" href="#multi-page-website">Multi-page website</a>
  <small>Early to mid 2018</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Apr. 13th, 2018
</figcaption>
</figure>

**Tools:** Node.js, HTML, Travis CI, GitHub Pages · [Commit reference](https://github.com/30-seconds/30-seconds-of-code/commit/dba34017e863a1adb150325b01a659e17e714704)

As the project grew, several new ideas were tested. Alongside website build automation, a [Rollup](https://rollupjs.org/) [script](https://github.com/30-seconds/30-seconds-of-code/blob/dba34017e863a1adb150325b01a659e17e714704/scripts/rollup.js) was used to create a **JS package** with all the code snippets and [tap](https://www.npmjs.com/package/tap) was used to add **test cases** for each snippet. These were part of the **automated workflows** running still on Travis CI. The [JS package](https://www.npmjs.com/package/30-seconds-of-code) was also released on [npm](https://www.npmjs.com/), but was discontinued at a later time.

New [static parts](https://github.com/30-seconds/30-seconds-of-code/tree/dba34017e863a1adb150325b01a659e17e714704/static-parts) were added to split the page into **multiple pages** at this point, and a redesign was implemented to make the page a little better visually, showcasing statistics and contributors. Some **dynamic content** was added, such as daily picks on the homepage and [data retrieved via GitHub's API](https://github.com/30-seconds/30-seconds-of-code/blob/dba34017e863a1adb150325b01a659e17e714704/scripts/web.js#L133-L150), using a GitHub token same as other processes.

Finally, Travis CI was set up to run using a **cron job schedule** as well as **push events** on the `master` branch, allowing for daily builds to occur at a predetermined time.

<details>
<summary> <h3>Trivia</h3> </summary>

2018 was a year of experimentation. New **side projects** came and went, such as a localization effort, a JS glossary and a snippet archive. Traces of these experiments can be found in the commit history of the repository.

At this time, the website was only comprised of the JS snippets and nothing else. Similar projects for CSS and Python were actively supported by some of the contributors, but had full autonomy to pursue their own paths. This decentralized organization put together without much planning would later come to bite me.

</details>

<h2>
  <a id="website-facelift" href="#website-facelift">Website facelift</a>
  <small>Late 2018</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Dec. 19th, 2018
</figcaption>
</figure>

**Tools:** Node.js, HTML, Travis CI, GitHub Pages · [Commit reference](https://github.com/30-seconds/30-seconds-of-code/commit/e89d5fab140d00e37750ad8a44eabb1cb45d3a05)

Later in 2018, the website was **redesigned** yet again. The dynamic parts were removed, the **search system** was reintroduced and the website style's were finally [completely custom](https://github.com/30-seconds/30-seconds-of-code/blob/e89d5fab140d00e37750ad8a44eabb1cb45d3a05/docs/scss/style.scss), written in [Sass](https://sass-lang.com/).

Not much had changed in the automated workflows, except for the introduction of some intermediary steps. One such step was the creation of a [JSON file](https://github.com/30-seconds/30-seconds-of-code/blob/e89d5fab140d00e37750ad8a44eabb1cb45d3a05/snippet_data/snippets.json) via the [extractor script](https://github.com/30-seconds/30-seconds-of-code/blob/e89d5fab140d00e37750ad8a44eabb1cb45d3a05/scripts/extract.js), which could then be used as the data source for other tasks. The [Travis CI configuration file](https://github.com/30-seconds/30-seconds-of-code/blob/e89d5fab140d00e37750ad8a44eabb1cb45d3a05/.travis.yml) now contained about a dozen scripts and this was a way to speed up a lot of them.

Another experiment that was running at the time was a [snippet collection for VS Code](https://marketplace.visualstudio.com/items?itemName=30-seconds.30-seconds-of-code), generated by [another script](https://github.com/30-seconds/30-seconds-of-code/blob/e89d5fab140d00e37750ad8a44eabb1cb45d3a05/scripts/vscodegen.js). Unfortunately, publishing the collection could not be automated and required a very slow process every time a new version needed to be published. This was also discontinued later down the line.

<details>
<summary> <h3>Trivia</h3> </summary>

[DigitalOcean](https://www.digitalocean.com/) **sponsored the project** back in 2018, providing a handful of cloud machines to work with. Unfortunately, this sponsorship was never utilized and the website never got the much awaited snippet API that it was expected to get. Another sponsorship from DigitalOcean came through the next year, but unfortunately it suffered the same fate.

</details>

<h2>
  <a id="gatsby-introduction" href="#gatsby-introduction">Gatsby introduction</a>
  <small>Late 2019</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Oct. 31st, 2019
</figcaption>
</figure>

**Tools:** Gatsby, React, Travis CI, Netlify · [Commit reference](https://github.com/30-seconds/30-seconds-of-code/commit/f3258d4eea951690f61c42daede52c7650309909)

The first half of 2019 was mostly quiet. However, during the summer months, [Gatsby](https://www.gatsbyjs.com/) was all the rage, so naturally 30 seconds of code followed the hype. It was also a great opportunity to move towards using [React](https://react.dev/) instead of plain HTML, which was getting hard to work with.

<figure>



<figcaption>
  Part of the current website deployment setup in the current UI of Netlify
</figcaption>
</figure>

Instead of GitHub Pages, [Netlify](https://www.netlify.com/) was now the **platform** where the website would be deployed. This proved to be a good plan, as it allowed Travis CI to [execute the other scripts](https://github.com/30-seconds/30-seconds-of-code/blob/f3258d4eea951690f61c42daede52c7650309909/.travis.yml), whereas Netlify would [run Gatsby and deploy the website](https://github.com/30-seconds/30-seconds-of-code/blob/f3258d4eea951690f61c42daede52c7650309909/netlify.toml).

At this time, most side projects about other languages were starting to fall apart. The [30-seconds organization](https://github.com/30-seconds) seemed like the way forward, allowing **consolidation of all official repositories** and proper maintenance. Some [integration tools](https://github.com/30-seconds/integration-tools) and a [repository starter](https://github.com/30-seconds/30-seconds-starter) were provided, allowing new projects to run autonomously.

<figure>



<figcaption>
  Current DNS setup on Namecheap, showing old subdomain setup
</figcaption>
</figure>

Each repository maintained its own website instance, all under the same domain name, but individual **subdomains** (e.g. `css.30secondsofcode.org`). The infrastructure was pretty much the same for each repository, with unique elements such as logos and styles being maintained for each project. Subdomains were configured via the **Namecheap Advanced DNS** menu, linking each Netlify project to an appropriate subdomain.

While this allowed for every project to be presented in a similar manner, it complicated maintenance to the point where a simple text change would require 5 or more commits across different repositories. This was definitely non-feasible in the long-term.

<figure>



<figcaption>
  SSL panel in Netlify's current UI
</figcaption>
</figure>

At around this time, **HTTPS** was becoming mandatory for all websites to be trusted by users and browsers. I'm not 100% certain how I obtained my **SSL certificate** before this time (I think I used [Cloudflare](https://www.cloudflare.com/) at some point, but the details are lost to time), but Netlify is kind enough to provide you with SSL certificates for each website for free. Obviously, I used their service as soon as it was available to me and never looked back.

<details>
<summary> <h3>Trivia</h3> </summary>

A lot of commits during that time are from **unrelated histories** (other repositories). You can probably tell by looking at the README logo, which features a different 30 seconds stopwatch for each project.

Getting the rights to all the repositories was not easy. Some maintainers wanted to retain their autonomy, others were completely indifferent and some were nowhere to be found. One of the repositories' original owner had disappeared from GitHub and it took a lot of searching to track the down on Reddit and ask them to transfer the repository to the organization. Luckily, nobody had any issues with the transfer and the organization filled up with content.

During this time or a little while later, Travis CI was starting to move away from its free forever model, making it harder to keep using. Eventually, credits ran out and Travis CI was dropped entirely from the infrastructure.

</details>

<h2>
  <a id="website-consolidation" href="#website-consolidation">Website consolidation</a>
  <small>Early 2020</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Mar. 31st, 2020
</figcaption>
</figure>

**Tools:** Gatsby, React, Travis CI, Netlify, Git submodules, GitHub Actions · [Commit reference](https://github.com/30-seconds/30-seconds-web/commit/14d4655fe5aa6e9026ac7bf033a52029354a372a)

After contacting **Netlify** in late 2019, a generous **Open Source sponsorship** was provided. This matched their [Pro plan](https://www.netlify.com/pricing/), which allowed for far greater flexibility and expanded limits.

<figure>



<figcaption>
  30 seconds of code Open Source plan limits in current Netlify UI
</figcaption>
</figure>

As mentioned previously, maintaining a handful of repositories and websites was close to impossible. The decision was taken to move the website infrastructure to its own repository (30-seconds-web) and use [Git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) to fetch the [content repositories](https://github.com/30-seconds/30-seconds-web/blob/14d4655fe5aa6e9026ac7bf033a52029354a372a/.gitmodules) at build time.

As [GitHub Actions](https://github.com/features/actions) were available at the time, it was decided that deployment to Netlify would be **triggered via pushing** to the `production` branch. This would ultimately result in the [deployment](https://github.com/30-seconds/30-seconds-web/blob/14d4655fe5aa6e9026ac7bf033a52029354a372a/.github/workflows/deploy-production.yml) action adding an **empty commit** to the branch and forcing it to update. Netlify would then trigger a build from the branch and deploy the website.

**Git submodules** were tricky to work with, but the main advantage was that updating them to the latest commit for each repository was trivial via a [script](https://github.com/30-seconds/30-seconds-web/blob/14d4655fe5aa6e9026ac7bf033a52029354a372a/src/jobs/fetch_content_sources.sh) that could run on Netlify.

```shell
git submodule update --recursive --remote
```

What this meant was that Netlify could always fetch the latest commits from [all repositories](https://github.com/30-seconds/30-seconds-web/tree/14d4655fe5aa6e9026ac7bf033a52029354a372a/content/sources) and the web infrastructure repository didn't need to do anything. Additionally, development was also pretty simple and fetching the latest snippet data was only a few keystrokes away.

In order to make this work correctly, a [`prebuild` command](https://github.com/30-seconds/30-seconds-web/blob/14d4655fe5aa6e9026ac7bf033a52029354a372a/package.json#L78) was added to `package.json`, performing the content update whenever `npm build` (the Gatsby build) was run.

Some additional utilities and functionality were introduced at this time, such as a pretty decent **search engine**, a snippet **ranker**, a **recommendation system** etc. Some of these tools are still in use today with minor changes to [their code](https://github.com/30-seconds/30-seconds-web/tree/14d4655fe5aa6e9026ac7bf033a52029354a372a/src/engines).

<details>
<summary> <h3>Trivia</h3> </summary>

Some **new JS tools** were introduced at this time, such as Storybook and Jest for testing, alongside a handful of configuration files and utilities.

Most of the structure and logic of the original setup for the web infrastructure holds true to this day. Due to framework changes, a lot of the current conventions are a jumble of different conventions, picked up from each new tool along the way.

Until the next milestone (pun intended), numerous releases and changes were made to the web infrastructure. These are far too many and complex to talk about and some of the details have faded in my memory. Feel free to browse [30-seconds-web releases](https://github.com/30-seconds/30-seconds-web/releases) prior to [v.6.0.0](https://github.com/30-seconds/30-seconds-web/releases/tag/v6.0.0) to check them out for yourself.

During this stage, I had to make some **DNS changes**, if I remember correctly. To cut a long story short, I made a mistake that meant the website would never go live on my domain. After a very long chat with Namecheap's support, I realized that it wasn't a propagation issue that kept my website down for half a day, but a typo on my part.

</details>

<h2>
  <a id="replacing-gatsby-with-nextjs" href="#replacing-gatsby-with-nextjs">Replacing Gatsby with Next.js</a>
  <small>Early 2021</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Jul. 23rd, 2021
</figcaption>
</figure>

**Tools:** Next.js, React, Netlify, Git submodules, GitHub Actions · [Commit reference](https://github.com/30-seconds/30-seconds-web/commit/e7b2b1ebfea7727a6b50add410d285ad6bc2945f)

Gatsby's issues kept on piling up for quite a while. From the inability to upgrade between minor versions to slow [GraphQL](https://graphql.org/) queries and tons of bugs and broken builds, I sought a better tool for quite some time. [Next.js](https://nextjs.org/) was quite popular at the time and was also based on top of React and supported by [Vercel](https://vercel.com/). It seemed like a match made in heaven.

The core philosophy was still almost the same - Git submodules for content fetched by Netlify for Next.js deployments triggered via the `production` branch scheduled or triggered via a GitHub Action.

Deployments were now triggered by pushes to the `master` branch or by a cron job **scheduled daily**. Additionally, a [manual trigger](https://github.com/30-seconds/30-seconds-web/blob/e7b2b1ebfea7727a6b50add410d285ad6bc2945f/.github/workflows/deploy-production.yml) was provided to allow for emergency deploys to be made without the need of an extra commit added to .

The extraction process used at the time might be worth explaining briefly, as it ties into the next milestone. A [handful of parsers](https://github.com/30-seconds/30-seconds-web/tree/e7b2b1ebfea7727a6b50add410d285ad6bc2945f/src/blocks/parsers) were created to read and parse files, converting Markdown and plaintext into **JSON objects**. The result of parsing these content files was ultimately some instances of a few JS classes, representing the [main entities](https://github.com/30-seconds/30-seconds-web/tree/e7b2b1ebfea7727a6b50add410d285ad6bc2945f/src/blocks/entities) of the project. Some concepts such as [adapters](https://github.com/30-seconds/30-seconds-web/tree/e7b2b1ebfea7727a6b50add410d285ad6bc2945f/src/blocks/adapters) and [decorators](https://github.com/30-seconds/30-seconds-web/tree/e7b2b1ebfea7727a6b50add410d285ad6bc2945f/src/blocks/decorations/withRecommendations) were borrowed from other frameworks. Finally, [serializers](https://github.com/30-seconds/30-seconds-web/tree/e7b2b1ebfea7727a6b50add410d285ad6bc2945f/src/blocks/serializers) would take the enriched data and produce pages for each snippet, along with listings and collections.

On a side note, there's a [huge list of redirects](https://github.com/30-seconds/30-seconds-web/blob/e7b2b1ebfea7727a6b50add410d285ad6bc2945f/public/_redirects) that is used by Netlify. This is pretty important for SEO, as it **redirects old URLs to new ones**. There are lots of artifacts in there, such as language renames, listing sorting removals, snippet renames etc. Take a look for a slice of the project's history.

Another interesting addition is the [Twitter bot](https://github.com/30-seconds/30-seconds-web/blob/e7b2b1ebfea7727a6b50add410d285ad6bc2945f/.github/workflows/tweet.yml) workflow. This GitHub Action used a JSON file that was **randomized each deployment**, generated by a [script called chirp](https://github.com/30-seconds/30-seconds-web/blob/e7b2b1ebfea7727a6b50add410d285ad6bc2945f/src/blocks/serializers/chirp/index.js). This allowed only specific snippets to be eligible and made their information available to the bot that run at specific times to post on Twitter. In order to post to Twitter, certain secret environment variables were added to the repository for the [Twitter API](https://developer.twitter.com/en/docs/twitter-api) and [Unsplash API](https://unsplash.com/developers).

Finally, the website's **sitemap** was generated manually via [a script](https://github.com/30-seconds/30-seconds-web/blob/e7b2b1ebfea7727a6b50add410d285ad6bc2945f/src/blocks/serializers/sitemap/index.js). While Next.js (and possibly Gatsby) provide convenient options to do this, not being bound to the framework except for page generation would prove to be a wise decision.

<h2>
  <a id="custom-orm-like-solution" href="#custom-orm-like-solution">Custom ORM-like solution</a>
  <small>Early 2022</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Mar. 29th, 2022
</figcaption>
</figure>

**Tools:** Next.js, React, jsiqle, Netlify, Git submodules, GitHub Action · [Commit reference](https://github.com/30-seconds/30-seconds-web/commit/9affd679c0a71173c1bbcc40206183c3d2b11f34)

While the infrastructure was mostly stable, the build process was slow and clunky. Inspired by Object-Relational Mappings (ORMs) (mainly [Ruby on Rails](https://rubyonrails.org/)' [ActiveRecord](https://guides.rubyonrails.org/active_record_basics.html#what-is-active-record-questionmark)), I developed [jsiqle](https://github.com/Chalarangelo/jsiqle), which somewhat resembled an **ORM** approach to what I was previously doing with Object-Oriented Programming (OOP).

While the tool's codebase is very complicated (almost unnecessarily so), the principles are simple. **Models** are used to model individual entities with specific attributes. These models can then define **relationships**, methods and derived properties and are grouped together in a schema. **Serializers** are provided to turn these models into plain JS objects that can be written into JSON files.

Using jsiqle removed a lot of the complexity from the website repository, allowing for simple [models](https://github.com/30-seconds/30-seconds-web/tree/9affd679c0a71173c1bbcc40206183c3d2b11f34/src/blocks/models) and [relationship definitions](https://github.com/30-seconds/30-seconds-web/blob/9affd679c0a71173c1bbcc40206183c3d2b11f34/src/blocks/schema.js), as well as very clean [serializers](https://github.com/30-seconds/30-seconds-web/tree/9affd679c0a71173c1bbcc40206183c3d2b11f34/src/blocks/serializers). An [extraction process](https://github.com/30-seconds/30-seconds-web/blob/9affd679c0a71173c1bbcc40206183c3d2b11f34/src/blocks/extractor/index.js) handles parsing Markdown files into a large JSON (stored temporarily in `.content/conten.json`) that is then used by the [application entry point](https://github.com/30-seconds/30-seconds-web/blob/9affd679c0a71173c1bbcc40206183c3d2b11f34/src/blocks/application.js) to [populate the in-memory dataset](https://github.com/30-seconds/30-seconds-web/blob/9affd679c0a71173c1bbcc40206183c3d2b11f34/src/blocks/application.js#L385). These two steps in the preparation process could be run separately and the output of the first one could also be easily inspected.

A final step is performed via the use of serializers and writers, storing all content in individual **JSON files** inside a directory. This is then used by [Next.js's helpers](https://github.com/30-seconds/30-seconds-web/blob/9affd679c0a71173c1bbcc40206183c3d2b11f34/src/pages/%5Blang%5D/s/%5Bsnippet%5D.jsx) to get the paths of each page to generate.

The application entry point in itself contains a lot of magic and complicated code to make development easier and faster. Models, serializers and utilities, for example, are **loaded automatically** and **exposed globally** via the `Application` object, allowing them to be accessed anywhere.

One of the conveniences afforded by jsiqle is the ability to have an interactive Node.js terminal (via the [`npm console`](https://github.com/30-seconds/30-seconds-web/blob/9affd679c0a71173c1bbcc40206183c3d2b11f34/package.json#L61) command). This allows developers to **query the extracted dataset**, much like they can do in the Ruby on Rails console.

<details>
<summary> <h3>Trivia</h3> </summary>

jsiqle originally stood for *JavaScript In-memory Query Language with Events*. The "with Events" part is no longer true, though.

Due to the way a lot of the website works, **Google Analytics** were only loaded if the user has accepted cookies. Some [complex and quirky code](https://github.com/30-seconds/30-seconds-web/blob/9affd679c0a71173c1bbcc40206183c3d2b11f34/src/pages/_app.jsx) had been added to send those initial page views, when appropriate.

If you look at the document template for Next.js, you will notice [this line](https://github.com/30-seconds/30-seconds-web/blob/9affd679c0a71173c1bbcc40206183c3d2b11f34/src/pages/_document.jsx#L14) (`<script>0</script>`). To this day, I have no clue why this worked or who suggested it, but it fixed some really obscure server-side rendering (SSR) problem or something similar.

</details>

<h2>
  <a id="replacing-nextjs-with-astro" href="#replacing-nextjs-with-astro">Replacing Next.js with Astro</a>
  <small>Early 2023</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Apr. 10th, 2023
</figcaption>
</figure>

**Tools:** Astro, jsiqle, Netlify, Git submodules, GitHub Actions · [Commit reference](https://github.com/30-seconds/30-seconds-web/commit/4dbad9aa9c2cdb6267eeeb92518067e7e4d3c6be)

Next.js was starting to get in the way more so than anything else. React was also not necessary in most cases, slowing down the page. A return to **static site generation** (SSG) was justified, and [Astro](https://astro.build/) was the perfect tool, combining flexibility and simplicity.

The migration from Next.js was surprisingly easy and adapting React components to Astro ones didn't take nearly as long as I expected. This resulted in a very **similar repository structure** and build process as the one before it.

A point of relative interest at this stage would be the [omni search component](https://github.com/30-seconds/30-seconds-web/blob/4dbad9aa9c2cdb6267eeeb92518067e7e4d3c6be/src/components/Omnisearch.js), built using the Web Components standard, allowing for a very dynamic component written in plain JS, without React or other similar frameworks.

<figure>



<figcaption>
  Website deploys in current Netlify UI
</figcaption>
</figure>

As Astro is essentially a templating language with a static site generator built-in, builds were now running faster and the website's **performance and SEO** were improved.

Speaking of performance, both Astro and Next.js provide some asset optimization tooling, yet I chose to [roll up my own](https://github.com/30-seconds/30-seconds-web/blob/4dbad9aa9c2cdb6267eeeb92518067e7e4d3c6be/src/blocks/writers/assetWriter.js). Assets are loaded from a content repository and are then processed via [sharp](https://sharp.pixelplumbing.com/), producing lower resolution images ready for the web, as well as images in the [WebP format](https://developers.google.com/speed/webp). These are then stored in a temporary directory (`.assets`) and used for development, without being regenerated every time the `predev` command runs (already generated assets are skipped). If the build is in a **production environment** (Netlify), the assets are also copied to the `public` directory to be served as part of the website.

<details>
<summary> <h3>Trivia</h3> </summary>

Initially, I was looking into [Svelte](https://svelte.dev/) and [SvelteKit](https://kit.svelte.dev/) as a replacement for React and Next.js. I stumbled upon Astro by accident and decided to give it a quick try. Surprisingly, I liked it far more than I expected and, due to a very **simple migration process**, I opted to use it.

</details>

<h2>
  <a id="content-repository-merge" href="#content-repository-merge">Content repository merge</a>
  <small>Mid 2023</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Jul. 16th, 2023
</figcaption>
</figure>

**Tools:** Astro, jsiqle, Netlify, Git submodules, GitHub Actions · [Commit reference](https://github.com/30-seconds/30-seconds-web/commit/01fc99181b01a8b208d06aa399e40ac2bb6551f7)

As community contributions were way lower, maintaining a handful of content repositories wasn't justified. Additionally, structural changes to how Markdown files were written required committing changes to 5 different repositories - a problem I was all too familiar with.

Merging all content repositories sounded exciting, yet I had no idea how to do so or if it was even possible. In fact, I thought it wasn't possible at all. Luckily, I found [an article](https://hacks.mozilla.org/2022/08/merging-two-github-repositories-without-losing-commit-history/) online outlining the process. What it all boils down is essentially adding the other repository as a remote locally and merging its `master` branch into the current `master` branch with the `--allow-unrelated-histories` flag.

```shell
git merge swtest/gh-pages --allow-unrelated-histories
```

After that point, the website repository's [submodules](https://github.com/30-seconds/30-seconds-web/blob/01fc99181b01a8b208d06aa399e40ac2bb6551f7/.gitmodules) were removed and only one was left in order to allow for the automated content fetching on Netlify to continue as it always did.

<details>
<summary> <h3>Trivia</h3> </summary>

**Merging unrelated histories** makes it really hard to follow changes and makes the git tree almost unusable. It also makes issue and pull request references reference the new repository instead of the original one. Don't do it, kids, it's not worth it most of the time.

</details>

<h2>
  <a id="website-and-content-repository-merge" href="#website-and-content-repository-merge">Website & content repository merge</a>
  <small>Late 2023</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Sep. 25th, 2023
</figcaption>
</figure>

**Tools:** Astro, jsiqle, Netlify, GitHub Actions · [Commit reference](https://github.com/30-seconds/30-seconds-of-code/commit/0b2b29c27948fd03112d38896776791bc2bdfc75)

After all the changes back and forth, it was starting to become clear that the separation between website infrastructure and content made little to no sense. The unrelated histories merge strategy was used once again, retaining the exact structure of the website repository. The `content` directory, previously containing git submodule references, now contained the raw content files.

This change created some new challenges, such as builds only triggering for website code changes and not for content changes. Accomplishing this required a simple revamp of the GitHub Actions I had created.

The scheduled and manual deploy workflows were [merged into one](https://github.com/30-seconds/30-seconds-of-code/blob/0b2b29c27948fd03112d38896776791bc2bdfc75/.github/workflows/deploy-production.yml). This **workflow** triggers as needed via cron or manual action and will always trigger a deployment to Netlify via a commit in the `production` branch. The [push-based workflow](https://github.com/30-seconds/30-seconds-of-code/blob/0b2b29c27948fd03112d38896776791bc2bdfc75/.github/workflows/deploy-production-push.yml), however makes use of the [Paths Changes Filter](https://github.com/marketplace/actions/paths-changes-filter), along with some **rules** to only trigger deployments if any of the website-related files have changed. This prevents minor changes in content files triggering a deployment. Finally, both workflows now output the [triggering event](https://github.com/30-seconds/30-seconds-of-code/blob/0b2b29c27948fd03112d38896776791bc2bdfc75/.github/workflows/deploy-production-push.yml#L25) as part of the commit message, so that it's easier to track what caused a deployment.

<figure>



<figcaption>
  Changing the deployment repository in Netlify's UI
</figcaption>
</figure>

Another point of interest is the changes that I had to make to the **Netlify configuration**. I was very skeptical if this would require a lot of manual work, but luckily I could change the repository I deploy from via the platform's UI. This only took a couple of minutes, caused no downtime for the website and needed no DNS changes, as everything was already configured.

<details>
<summary> <h3>Trivia</h3> </summary>

I was at the time working on some new GitHub Actions that would allow for pull request QA checking, making content curation much simpler. These would be canned later, as community contributions were put on the back burner.

Towards the end of this period, I transferred the 30-seconds-of-code repository back to my personal account. **Links to the previous URL** will link to the new one, as long as the **organization** still exists on GitHub, which is the reason why I'm not deleting it.

</details>

<h2>
  <a id="quality-of-life-improvements" href="#quality-of-life-improvements">Quality of life improvements</a>
  <small>Late 2023</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Dec. 20th, 2023
</figcaption>
</figure>

**Tools:** Astro, jsiqle, Netlify, GitHub Actions · [Commit reference](https://github.com/Chalarangelo/30-seconds-of-code/commit/f7ed6b420f6c1c5984c511b813c00fe1b3b6fcda)

During the last few months of 2023, a lot of quality of life changes were made to the website, affecting end users (UI changes), authors (content creation tooling), infrastructure (content parsing and building) and development (**hot reloading** and content queries).

Some changes that were made include, but are not limited to, [an updated MarkDown parser](https://github.com/Chalarangelo/30-seconds-of-code/commit/9e4c3b62f78d4bb6c069ce0f169108322c1a568e), the addition of [automated CodePen embeds](https://github.com/Chalarangelo/30-seconds-of-code/commit/c54928cb23749ad42af3d27100d666e6f8a11539), [GitHub-style admonitions](https://github.com/Chalarangelo/30-seconds-of-code/commit/ec7fc8fed546a91613d6c2bc58cd5408ae31c17c), [watch mode for content hot reloading](https://github.com/Chalarangelo/30-seconds-of-code/commit/73165a2a767b8db4e317edbccde64e2834f998a7), and [prepared queries for the console](https://github.com/Chalarangelo/30-seconds-of-code/commit/2f63fa20ff22d6bc15dd1948466aee9fe05ed708).

Additionally, the codebase was **migrated to ESM**, while a rewrite and restructure was done to ensure everything worked as intended. Finally, some **unnecessary integrations** (Google Ads, Google Analytics) and infrastructure parts (icon font, second content font, multiple format images, executable snippet code) were removed from the codebase altogether.

<details>
<summary> <h3>Trivia</h3> </summary>

Some of the [remark](https://github.com/remarkjs/remark) **plugins** were (and still are) written in a very trial-and-error sort of way, where I try to figure out what nodes are produced by the parser and what I can do to transform them. I often resort to adding raw HTML into the output, which is not ideal and could definitely be improved.

[CodePen](https://codepen.io/) embeds needed some styling, as to not clash with the website color palette. The [documentation](https://blog.codepen.io/docs/embeds/themes/) for embedding and using them is scattered around, outdated and often incorrect. I had to delve into code and tinker a bit with them to get it right.

**Watch mode** was implemented in an arbitrary fashion, making it slow and a little fiddly. However, it provided enough utility to keep around until later iterations, where I could make it more elegant and practical for my needs.

</details>

<h2>
  <a id="rewrite-in-ruby-on-rails" href="#rewrite-in-ruby-on-rails">Rewrite in Ruby on Rails</a>
  <small>Mid 2024</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Jul. 25th, 2024
</figcaption>
</figure>

**Tools:** Astro, Ruby on Rails, SQLite, Netlify, GitHub Actions · [Commit reference](https://github.com/Chalarangelo/30-seconds-of-code/commit/227f510a40a8e4976fa38e154435a60574dc456b)

After about 6 months of working on cleaning up and consolidating content, I decided to try an infrastructure change, namely rewriting all of the backend code in [Ruby on Rails](https://rubyonrails.org/). The idea was to create a JS - Ruby on Rails - JS (Astro) sandwich.

The first JS part would be responsible for **parsing MarkDown and highlighting content**, much like before, so I wouldn't have to replace this part of the infrastructure, which worked well. The output of this step would be a **JSON file**, same as before. Some optimizations were made to parallelize code execution and separate this logic into what was essentially a standalone module that could run, process input, finish and exit.

Ruby on Rails would then **replace the jsiqle part**, using its [ActiveRecord](https://guides.rubyonrails.org/active_record_basics.html) and [SQLite](https://sqlite.org/) to load the JSON data into a **persistent storage**. The SQLite storage would be treated as a throwaway layer, meaning it would be regenerated on application restart. This allowed the code to interface nicely with the latest data and easily use ActiveRecord helpers.

Finally, rake tasks and serializers were used to output JSON data in the same structure as before to then feed into Astro.

<details>
<summary> <h3>Trivia</h3> </summary>

While this approach made it a lot easier to query data and optimize structures, it was relatively slow, mainly due to the **I/O overhead** needed for the additional SQLite step.

Build times with Ruby on Rails were about 3x slower than JS prior to them and about 4x slower than the optimized JS code that shipped after the next refactor.

Migrating from JS to Ruby on Rails provided a great opportunity to **separate concerns** and build steps into the initial parsing (JS), data processing for serialization (Ruby on Rails) and Static Site Generation (Astro). This allowed for further refactors, where these three steps are separate.

Due to the way the refactored codebase was set up, I never managed to crack the code of how to add a watch mode. This, however, wasn't a problem, as this setup was really short-lived.

</details>

<h2>
  <a id="rewrite-back-in-plain-js" href="#rewrite-back-in-plain-js">Rewrite back in plain JS</a>
  <small>Late 2024</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Dec. 14th, 2024
</figcaption>
</figure>

**Tools:** Astro, Netlify, GitHub Actions · [Commit reference](https://github.com/Chalarangelo/30-seconds-of-code/commit/1d4f3e031094a57dc7af6e02306505904ba0f6f1)

Seeing that Ruby on Rails was slow for my use case, I started migrating the Ruby code back to JS. The Ruby on Rails directory structure provided a great way of organizing files, logic and figuring out what goes where, so I kept most of the conventions and structures intact.

Porting from Ruby back to JS took about a week, right before the summer break, and it was not particularly hard, as I was relatively fluent in both languages. The **performance** improved quite a bit, even compared to the setup I had prior to the Ruby on Rails rewrite.

A notable update at this time is the **custom model system** I developed, inspired by the previous ActiveRecord implementation, which is thoroughly documented in the [Modeling complex JavaScript objects](https://www.30secondsofcode.org/js/complex-objects/p/1/) series of articles, written in late 2024. This system essentially replaced jsiqle, which was far too slow due to the usage of `Proxy` and being agnostic, while a custom-tailored built-in solution was way faster. The performance increase was only compounded by the use of **new JS features** (e.g. [static initialization blocks](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks)) that made the code more robust, less verbose and significantly more customizable.

I kept working on small changes, while revising content, for a few months, most notably adding a **better watch mode**. The new watch mode watches all files under the `content` directory for changes. When a change is detected, the JSON data needed for Astro is recalculated on the fly, by rerunning the entire processing pipeline, skipping assets and other things that aren't important to improve response times.

<details>
<summary> <h3>Trivia</h3> </summary>

**Notifying Astro about changes** in content was tricky. To circumvent the issue, I devised a trick where a `timestamp.js` file is [generated](https://github.com/Chalarangelo/30-seconds-of-code/commit/109a3c06609807eaf88b5fb3edb0b6efb770e65d) at the end of each content rebuild. This is then [imported](https://github.com/Chalarangelo/30-seconds-of-code/commit/e450370c4e070a45fb00000647c787060646a6e5) into Astro, so that it notices a dependency change and it will reload the page.

The new watch mode delivered a **near-instant editing experience**, where switching from VS Code to Chrome (literally under a second) would be enough to rebuild the content and trigger an Astro page refresh, on a MacBook Air M1.

During this time, the **Update Logs** functionality and announcement on the home page was introduced, allowing for a more personal touch.

While the **2-step content preparation** (MarkDown parsing, serialization from models) was still made up of distinct, decoupled steps, the watch mode didn't make use of this separation. To this day, the separation of the layers is intact, yet only needed as a means to debug parts of the pipeline more easily during development of new features.

In late December, I redesigned [my personal portfolio website](https://www.chalarangelo.me/) to match the style and color palette of 30 seconds of code, as part of the greater strategic shift in the **brand narrative** (30 seconds of code now being a personal blog and all that) to feel more concise and like part of the website itself. Fun fact: My personal portfolio website also doubles as a printable CV, with custom print styles to be easily exported to PDF.

</details>

<h2>
  <a id="functionality-extensions" href="#functionality-extensions">Functionality extensions</a>
  <small>Early 2025</small>
</h2>

<figure>



<figcaption>
  30 seconds of code website circa Jun. 11th, 2025
</figcaption>
</figure>

**Tools:** Web Components, Astro, Cloudflare, GitHub Actions · [Commit reference](https://github.com/Chalarangelo/30-seconds-of-code/commit/dff11ff1b2658e5c54783afd5e8459281728c698)

In the months that followed, I focused more on content rather than infrastructure. However, as I focused on larger projects, I also liked tackling more challenging tasks, which also lead to some infrastructure projects that really upgraded the website.

One such project was the [complete search overhaul](https://github.com/Chalarangelo/30-seconds-of-code/commit/91e518b9861ba42ba67b2f451bd69ab674ec9d04), as the previous search system dated back to early 2020 and was **severely underpowered** for the now long-form content and the the sheer amount of articles it had to serve. I developed a new Natural Language Processing (**NLP**) solution with TF-IDF for this purpose, building a [document index](https://github.com/Chalarangelo/30-seconds-of-code/commit/3d39921715380d59e12d0fc413a2e24866794e38), inspired by [ElasticSearch](https://www.elastic.co/elasticsearch), which was later enhanced with [partial matching via n-grams](https://github.com/Chalarangelo/30-seconds-of-code/commit/a1347e49664eeadfe9da6c809a31ea094a0170b0). The new search journey is documented in [this series of articles](https://www.30secondsofcode.org/js/search/p/1/), written at around the same time as the implementation.

Another major overhaul was the removal of [PrismJS](https://prismjs.com/) in favor of [Shiki](https://shiki.style/) for code highlights. This provided better grammars and syntax highlighting, allowing for more accurate, VS Code-like highlights. It also unlocked many new features, such as [bracket pair colorization](https://github.com/Chalarangelo/30-seconds-of-code/commit/d411ae205fe12c8941dd94fd8844d408c1529b75), [line highlights](https://github.com/Chalarangelo/30-seconds-of-code/commit/dda86b50796ba07ffad03fd38edc4589c1e9a1d2), [collapsible code blocks](https://github.com/Chalarangelo/30-seconds-of-code/commit/08a2313be3d5370d4a4b5cb2a8cfdcd9cd72f39e) etc.

Moreover, another addition was support for **Web Components** to enhance the content on various pages. These include, but are not limited to, the [table of contents scroll spy](https://github.com/Chalarangelo/30-seconds-of-code/commit/7afca7cdbf3116109225cf575a2373b9266bf61a), [code tabs](https://github.com/Chalarangelo/30-seconds-of-code/commit/acbba90f58edef0ec50bc63c6c6f9f4eac0ad142), dynamic [LaTex support](https://github.com/Chalarangelo/30-seconds-of-code/commit/77c59bf72ca0caaf63b030745fa4cf9fee210042), [Baseline support](https://github.com/Chalarangelo/30-seconds-of-code/commit/b399ce4ccd0be3e1affce0666afafe4b485167d8) and [step visualization](https://github.com/Chalarangelo/30-seconds-of-code/commit/760362746933ffb01f5cd8492d76a701e2de10be). All components are [loaded dynamically](https://github.com/Chalarangelo/30-seconds-of-code/commit/244ef625fb14e873fe86864218e20c864bd2d5b0), as needed, alongside their CSS via ECMAScript Modules (ESM), while having accessible fallbacks, in case anything goes wrong. Apart from these, I also developed [a way to link to different articles and collections via custom MarkDown syntax](https://github.com/Chalarangelo/30-seconds-of-code/commit/a56818a6ce5fcc01a47c6d5b4a673c2331e0b0ad), allowing for more interesting links between articles.

Finally, I moved the website to [Cloudflare Pages](https://pages.cloudflare.com/) for hosting, as it provided a better experience and more features than Netlify (namely **integrated Web Analytics** and **AI Bot protection**). The website is now served from [Cloudflare](https://www.cloudflare.com/)'s edge network, which provides faster load times and better performance. Their bandwidth and usage limits are pretty generous, so I don't expect to run into any issues with the current traffic any time soon. The whole migration process took just one evening, with most of the issues I faced being related to DNS setup that I'm not particularly good at.

<details>
<summary> <h3>Trivia</h3> </summary>

I plan to enhance the NLP setup to later introduce topic modeling, embeddings and dynamic personalized recommendations, based on the current user session.

The search system still runs on the client, using a serialized JSON. I kept the original setup mostly intact, however I had to find a good balance between quickly setting it up on the client and minimum network load. I settled a **frequency mapping system** that makes it easier to reconstruct the document index on the client, without having to serialize huge loads of data for it (e.g. `i:2 know:2 that nothing`).

Shiki's codebase is a little tricky to browse and the documentation wasn't very accessible for my needs. I ended up reading most of the codebase to make sense of it. I also didn't like how some official transformer plugins were implemented, so I rolled up my own whenever possible ([example](https://github.com/Chalarangelo/30-seconds-of-code/commit/836f0030422cf599bee6e48d1455f064a43cb26a)).

I wanted code tabs to work in the most frictionless way possible. This meant that I had to use a `details` element to wrap them, which made me resort to some pretty clever and complicated hacks to make them work. A simplified implementation is documented [here](https://www.30secondsofcode.org/css/s/details-tabbed-content/).

[LaTex](https://www.latex-project.org/) libraries are extremely heavy, so build times, especially in watch mode are terrible. It's not optimal to **load the library on the client side**, but it only happens in the few (~10) articles that need it and is also cached by the browser.

Remember how I removed PrismJS? Well, I lied... a little! Watch mode _still_ uses it in the form of a Web Component for faster edit time page refreshes. There is [now](https://github.com/Chalarangelo/30-seconds-of-code/commit/9ce26be52b8423da550268d0b929368edb4085a9) a `watch` mode and an `observe` mode, using PrismJS and Shiki respectively.

I [updated Sass to the latest version](https://github.com/Chalarangelo/30-seconds-of-code/commit/6f172b24f542be8b64731d9711bbeff51d7fa945) at this time and I gotta say their automatic migration tools and migration guidelines are a breeze to follow.

A lot of these later updates are documented in the website's [Update Logs](https://www.30secondsofcode.org/update-logs/p/1/).

</details>

---

Phew! That was a ton of information, especially considering it's almost 8 years worth of work, mistakes, learnings and experiments. I hope you found this article interesting and that you'll take away some useful insights that you can apply to your own projects. See you in the next one! <span class="wave">👋</span>

---

### Snippets Articles S Http Status Codes Cheatsheet (`content/snippets/articles/s/http-status-codes-cheatsheet.md`)

---
title: Common HTTP status codes Cheat Sheet
shortTitle: HTTP status codes
tags: [webdev,http,cheatsheet]
cover: hiking-walking
excerpt: Familiarize yourself with the most common HTTP status codes with this handy cheatsheet.
listed: true
dateModified: 2021-06-12
---

## 1xx - Informational

- **100**: Continue - Everything is ok so far
- **102**: Processing - Request is being processed, no response available yet

## 2xx - Success

- **200**: OK - Request successful
- **201**: Created - Request fulfilled, new resource has been created

## 3xx - Redirection

- **301**: Moved Permanently - Resource permanently moved to a new URL
- **302**: Moved Temporarily - Resource temporarily moved to a new URL

## 4xx - Client Error

- **400**: Bad Request - Server cannot understand and process the request
- **401**: Unauthorized - Authentication required, user not yet authenticated
- **403**: Forbidden - Insufficient access permissions to the resource
- **404**: Not Found - Requested resource not found
- **410**: Gone - Request no longer available due to intentional removal

## 5xx - Server Error

- **500**: Internal Server Error - Generic unhandled server error
- **502**: Bad Gateway - Gateway server got an invalid response
- **503**: Service Unavailable - Server temporarily unable to handle request
- **504**: Gateway Timeout - Gateway server didn't get a response in time

---

### Snippets Articles S Learnings From Vibe Coding A Small Web Project (`content/snippets/articles/s/learnings-from-vibe-coding-a-small-web-project.md`)

---
title: What I learned from vibe coding a small web project with GitHub Copilot and GPT-5 mini
shortTitle: Vibe coding learnings
tags: [webdev,programming]
cover: interior-19
excerpt: A short experiment to see what vibe coding feels like and how far AI can take a project without writing any code myself.
listed: true
dateModified: 2025-10-24
---

A few weeks ago, I decided to finally try **vibe coding**, mostly out of curiosity, but also due to necessity and a lack of time. The bet was to create a small web project using a coding assistant, without much manual coding.

## Setup

First things first, the tools I used for the job are what I'm most familiar with - **GitHub Copilot** and **VS Code's Agent Mode**. While I use GPT-4.1 as my daily driver (what a time to be alive, I'm calling an LLM my _daily driver_), I decided to give **GPT-5 mini** a go. Spoiler alert: both models seem to be on par, so we won't be focusing on that too much.

I'm not going to dive into the project details, but rather share some of the learnings and insights I gathered from this experience. All you need to know is the tech stack, which includes **Go** on the backend, **SQLite** for the database, and **React** with **Redux** and **Sass** on the frontend.

I should also mention that I'm familiar with all of these technologies, but I'm really a beginner in Go, so I could use the help in setting up the project structure and gluing things together.

## The good

Starting out with the good, the experience was surprisingly smooth. My little AI assistant was delightfully quick in setting up the **project structure** and **installing the dependencies** I wanted, without me having to lift a finger. Full disclaimer that I knew most of the toolkit I wanted to use, so it didn't have to guess much in the earlier stages.

I was pleasantly surprised by how well it handled **boilerplate setup** and **small refactors** I suggested. It was also very good at performing **basic tests** via `curl` commands and stuff like that to verify that the endpoints were working as expected.

Another thing I noticed is that I didn't need to dive too deep into the **documentation** of that many tools. As I'm sort of familiar with Go's ecosystem, I managed to find some dependencies that looked right. I didn't want to spend 40 hours **debugging** a live reloader and writing my database connection call, so I just asked the AI to do it for me. What would have taken me a day or two, took a couple of hours. Bonus points for not having to read through a ton of docs that I wouldn't need ever again during development of the project.

Last but not least, it was truly a **learning experience**. Seeing how the agent was restricted to **terminal commands** and the editor, I noticed how it did things I wouldn't have thought of or been used to. For example, I like how it used `curl` to test endpoints, or `lsof` to find process IDs, and actually picked up the trick myself after that!

## The bad

Ok, now onwards to what I didn't like. It's really **not that smart**! As soon as you have enough moving parts - and that's just a half a dozen things - it starts to **struggle, hallucinate, or get confused**. And don't even get me started on **indentation and formatting**, which is entirely impossible to get right on its part. I had to manually fix it in literally every single file it generated.

One example of its limitations became quite obvious when I was **debugging** one of the first endpoints in the project. The database was set up, there was a table with data and I had a working connection to it from Go. I even double checked via `sqlite3` on the terminal to see that everything was there. For some reason, the endpoint returned 404.

When I tried explaining it to Copilot, it **insisted** that the database connection was the problem, and suggested a fix that was completely off, almost erasing my database in the process. Yeah, I wasn't too happy about that!

I knew it wasn't database-related, so I prompted it a couple of times, but it kept insisting that the connection was the problem. I finally gave up and debugged it myself, realizing that the problem was actually in the live reloading tool. I told it where to focus and it sort of got it right, but not really, so I ended up patching the issue myself.

## The worse

This last example brings me to my next point, which is that, sadly, it gets even worse. Not only does the AI get stuck and obsessed over issues, **it won't even produce the correct fixes** in some cases, **update the right dependencies** or even **find the right documentation**.

I looked up the documentation of the live reloading package I was using and, as it turns out, CoPilot installed a fairly ancient and buggy version. I updated it and tried to fix the issue, only to realize that the configuration file it produced was very broken and, by some odd miracle, in the haze of terminal commands it ran before, it produced a working build just once.

I then proceeded to check my `package.json` file and, lo and behold, **every single dependency was outdated**. And I'm not even talking like minor versions here, it was older than a year. I understand this is reasonable, since the model was trained on data that is at least a few months old, but still, it was quite frustrating to have to update everything manually.

The worst part is it did try to list the latest versions of the dependencies on npm and then it proceeded to install older ones, presumably because it only had their documentation in its training data. Still, some of **the code it produced didn't even match the versions it installed**, resulting in a couple of simple, yet annoying issues here and there.

I won't even go into **best practices**, **security** issues and stuff like that, because it would take a whole other article to cover that. Just be aware that you should **always double check everything it produces**, as if it was a distracted junior developer with access to last year's internet.

## Other takeaways

Apart from having some fun and realizing how far AI has come, I also **learned a few solid programming things**, which is, I think, the most value I got from this experience.

Like I said before, I picked up the `lsof -i :8080 -t` trick to find the process ID of a server running on a specific port and `kill` it. I also spent some more time with tools like `sqlite3` and `curl`, due to the fact that the AI used them or prompted me to use them here and there. And, luckily, I refreshed my knowledge of some Go concepts and Makefiles, which I hadn't used in a little while.

I also like how it got me to focus on the project structure, tools, ecosystem and the big picture. As a beginner in Go, I would have struggled much more, resulting in me frustrated and thinking the ecosystem isn't there yet. Having said that, I still believe that I should have written the project in Ruby on Rails and not Go, as I'm not yet at the level where I could tackle it and maintain it properly. Still, I'm pretty sure I would have had a different outcome and perspective had I done it without the AI's assistance.

Finally, as I had the chance to experience a stack I haven't tried before, it solidified a couple of things in my head. The oddest one is that migrations, as a database concept, aren't limited to ORMs, but rather a general concept that can be applied to SQL databases, even on a low level. Yeah, I've been using them in Rails for a while, never thought about it that way. I also noticed how one sets up Overmind and Procfiles, which I had only seen in projects I had worked on, never set them up myself.

These small nuggets of knowledge or refreshers seem like **the most valuable takeaway for me and my growth** as a developer.

## Closing thoughts

If you are considering vibe coding, I would say give it a go, but it's quite the gamble. Be prepared for some frustration and for it to eventually become a hassle. LLMs are pretty helpful, but they definitely have a long way to go to replace a human developer, even a junior one. The best use case I could think of, writing boilerplate code, wasn't terrible, but anything more complex seems like a stretch right now.

Still, I think developers, especially junior and mid-level ones stand to gain a lot from watching the AI code and fill gaps in their knowledge. So, yeah, give it a go, but don't expect too much!

---

### Snippets Articles S Markdown Cheatsheet (`content/snippets/articles/s/markdown-cheatsheet.md`)

---
title: Markdown Cheatsheet
tags: [webdev,documentation,cheatsheet]
cover: typography
excerpt: Markdown is the most popular markup language for writing documentation. Here is a cheatsheet with the most common syntax.
listed: true
dateModified: 2025-06-08
---

Markdown is a lightweight **markup language** with **plain text** formatting syntax, easily convertible to various output formats, such as HTML. In the programming world, it's widely used to format **documentation**, README files, **GitHub issues, pull requests** and comments. While there are various flavors of Markdown, the basic syntax is shared across implementations.

> [!TIP]
>
> As most users will be using **GitHub Flavored Markdown (GFM)**, a more complete reference can be found [here](https://github.github.com/gfm/). You may also want to check out [Markdown Guide](https://www.markdownguide.org/ ) for a more comprehensive overview of Markdown syntax.

## Headings

```md
# Heading 1

## Heading 2

### Heading 3

#### Heading 4

##### Heading 5

###### Heading 6
```

## Paragraphs

```md
This is a paragraph.

This is another paragraph.
```

## Emphasis

```md
**Bold text** or __Bold text__

*Italic text* or _Italic text_

***Bold and italic text*** or ___Bold and italic text___
```

## Lists

```md
- Unordered list item 1
- Unordered list item 2
  - Nested unordered list item
- Unordered list item 3

* Unordered list item 1
* Unordered list item 2
  * Nested unordered list item
* Unordered list item 3

1. Ordered list item 1
2. Ordered list item 2
   1. Nested ordered list item
3. Ordered list item 3

1. Ordered list item 1
1. Ordered list item 2
   1. Nested ordered list item
1. Ordered list item 3
```

## Links

```md
[Link text](https://example.com)

[Link text with title](https://example.com "Title")
```

## Images

```md



```

## Code

````md
Inline code: `code`

```
Block code
```

````

## Blockquotes

```md
> This is a blockquote.
>
> It can span multiple lines.
```

## Tables

```md
| Header 1 | Header 2 |
|----------|----------|
| Row 1    | Row 1    |
| Row 2    | Row 2    |
```

## Horizontal Rule

```md
---
```

---

### Snippets Articles S No Code Is Inherently Evil (`content/snippets/articles/s/no-code-is-inherently-evil.md`)

---
title: No code is inherently evil
tags: [webdev,career,programming]
cover: coffee-n-laptop-3
excerpt: No code tools? No code at all? No code you write? Prepare for a triple entendre that cuts deep into the world of software development.
listed: true
dateModified: 2025-08-21
---

No code is inherently evil. That's not a typo. It's a **triple entendre**, and the more time you spend building software, the more each meaning resonates with you. Let's dig in together and see what makes this idea so fascinating.

## No code tools

Let's start with **no code tools**, an ever more-relevant topic in the age of AI. They promise to save you from the pain of writing code. Drag, drop, publish. It feels like magic, until it doesn't.

The honeymoon ends when you need to do something custom. Suddenly, you're staring at a wall of auto-generated spaghetti, or worse, a locked box with a single _escape hatch_ that dumps you into a world of pain.

Want to add a feature? Rewrite half the project. Want to integrate with something outside the walled garden? Prepare for third-party hacks, ballooning costs, and a maintenance nightmare.

No code tools are often just a **footgun**: easy to pick up, but just as easy to shoot yourself in the foot with. The easy start is a trap. The real cost comes later, when you need to scale, customize, or migrate.

## Analysis paralysis

But there's another kind of _no code_ evil: **writing no code at all**.

The **cult of best practice** tells us to analyze, plan, and optimize before we even start. We chase the _best_ framework, the _right_ architecture, the _perfect_ stack. We freeze, paralyzed by choice. Meanwhile, the real world moves on.

The truth? You learn more by shipping something _ugly_ than by endlessly debating what's _best_. Write code, even if it's _bad_. Ship it, break it, learn from it, and do it better the second time.

The first version is disposable. The second is informed by reality. After all, you can't refactor what you haven't written. Writing bad code is a **prerequisite** to writing good code. Don't skip steps. Experiment, reflect, iterate.

## Code itself

Finally, let's talk about the code itself. **No code you write is evil by default**. Code is just instructions. It's not malicious unless you make it so.

But here's the twist: even your _best_ code will eventually be stretched, twisted, and abstracted into something you never intended. Features change, requirements shift, and suddenly your _elegant_ solution is a Frankenstein's monster.

That's not evil; it's just entropy. The real evil is refusing to change, or worse, contorting your code into unrecognizable shapes to fit every edge case. Sometimes, the right move is to **write more code**, even if it means duplicating logic or starting over. **Refactor later**, when you know what actually matters.

@[You may also like](/articles/s/tech-stack-refactoring-problems)

Remember, code isn't sacred. It's a tool. Use it, break it, throw it away, and write more whenever you need to.

## Conclusion

So, no code is inherently evil. No code tools can trap you, if you're not careful. Endlessly pondering over best practices and writing no code leads to stagnation. And no code you write is evil, until you let it rot. Write more code. Ship early. Refactor when it hurts. That's how you build things that last.

---

### Snippets Articles S Tech Stack Refactoring Problems (`content/snippets/articles/s/tech-stack-refactoring-problems.md`)

---
title: How your tech stack is causing your refactoring problems
shortTitle: Tech stack refactoring problems
language: javascript
tags: [webdev,career,programming]
cover: laptop-hands
excerpt: Infinite flexibility in your tech stack can lead to refactoring headaches and technical debt. Here's how to avoid the trap.
listed: true
dateModified: 2025-10-08
---

Ever feel like your codebase is fighting you at every turn? **The tools you choose**, your programming language of choice, frameworks, and overall stack, shape how you build, refactor, and maintain software. Sometimes, the very flexibility that drew you in becomes the root of your biggest headaches. Let's dig into why your tech stack might be the silent culprit behind those endless refactoring sessions and mounting technical debt.

## Infinite flexibility, painful refactoring

**Flexible languages** like JavaScript and Python give you endless ways to solve a problem. That freedom feels empowering, but it comes with a catch. Every new feature, syntax, or method is a **potential footgun**. You can shoot yourself in the foot by designing APIs with **too many options**, **clever overloads**, or **many ways to do the same thing**. The result? Codebases that are **hard to reason about** and even harder to maintain.

When your codebase is a **patchwork of clever tricks** and alternative patterns, refactoring becomes a nightmare. You start with good intentions, but the **complexity grows**. Each refactor adds another layer of abstraction, another workaround, another set of edge cases. Instead of simplifying, you end up with a **tangled mess**. Debugging gets harder, productivity drops, and frustration rises.

## Opinionated stacks force better design

Languages like Go are (in)famously **opinionated**. They do not let you overload functions or invent alternative syntax for the same operation. You have to **pause and rethink your approach**. You are forced to design **explicit APIs** and **clear modules**. It feels **restrictive**, but it saves you from yourself. You spend less time refactoring and more time **building features that work**.

Ask yourself: do you need three ways to sort an array? Do you need methods with slightly different side effects? Is **infinite flexibility** helping, or is it just making your code harder to understand? Most of the time, a **simple, explicit API** is better. It is easier to **test**, easier to **debug**, and easier to **refactor** when you need to.

## Overengineering and technical debt

Before you add another method or another overload, **stop**. **Rethink your API**. Rethink your module boundaries. Would **splitting your code into smaller, focused modules** help? Would **making your API more explicit** reduce confusion? It is more painful now to rewrite something, but it is even more painful to **patch it over and watch the technical debt pile up**.

Trying to anticipate every possible future need leads to **overengineered solutions**. You add **hooks, options, and configuration** for things you might never use. As the codebase grows, **complexity mounts**, and soon you are crawling to a halt. **Technical debt** is not just a buzzword. It is the real cost of **poor design decisions** made in the name of flexibility.

@[Further reading](/articles/s/technical-debt)

## Keep it simple, force good design

Pause. **Redesign** your code. Make it simple. Split it into **modules** if needed. Force good design decisions. Make usage **explicit**. Favor **clear APIs** over clever ones. Every time you touch your code, make a small cleanup. You do not need refactor sprints. You just need to take care of your code, one change at a time.

A phrase I really like is "eat the frog". It means doing the hard thing first. Redesign, refactor, and **simplify before the debt piles up**. It is better to fix the problems early than to let poor technical decisions slow you down later. Your future self will thank you.

> [!NOTE]
>
> Apparently, "eat the frog" is a time management technique, that means tackling your most challenging or dreaded task first thing in the morning. I had only heard of it on YouTube and thought it was just a metaphor. Still, the point stands: tackle the hard stuff early to avoid bigger headaches later.

---

### Snippets Articles S Technical Debt (`content/snippets/articles/s/technical-debt.md`)

---
title: Technical debt
tags: [webdev,career,programming,management]
cover: new-york
excerpt: Learn what technical debt is, its causes and symptoms, and how to deal with it.
listed: true
dateModified: 2022-10-09
---

Technical debt is a jargon term that gets thrown around a lot in the software development industry. It's worth understanding what it means, how to spot it and how to deal with it, once it inevitably rears its ugly head.

## Definition

The term itself comes from a metaphor, in which the team's lack of understanding about the codebase leads to continuous disagreement. Continuously stumbling upon this situation slows down progress, feeling like paying interest on a loan.

**Technical debt is effectively the natural result of writing code about something we don't have a proper understanding of.**

## Causes

But what causes technical debt in the first place? As hinted at by the previous definition, it arises from a **disagreement between business needs and how the software was written**. The root cause for such disagreements can either appear early on or later down the line.

In the former case, a lack of understanding might have lead to laying the foundations incorrectly, therefore carrying the debt from the start. Poor design choices, lack of foresight and a lack of communication between the team members can all lead to this.

In the latter case, the debt might have been incurred due to a change in the business requirements, which the software was not designed to handle. This is often the case with projects that move rapidly, making it hard to adapt to the changing needs.

Interestingly enough, poor code and not following best practices only account for a portion of technical debt. This means that, while following best practices and writing clean code can help reduce the debt, they are usually not the main cause.

@[You may also like](/articles/s/tech-stack-refactoring-problems)

## Symptoms

So, how do you know if you have accumulated technical debt? It's not that hard to spot most of the time. **Lowered productivity** is usually one of the first signs. This translates to increased development time or effort estimates that are not met. This makes sense intuitively, as maintenance costs mount up and the codebase is not as easy to work with as it should be.

Following lowered productivity, teams with accumulated technical debt might start to notice **decreased code quality**. When estimates are not met, developers might be tempted to cut corners and write code that is not as clean as it should be. This is a natural reaction to the pressure of meeting deadlines, but it can have a negative impact on the codebase in the long run.

Finally, this spirals into a vicious cycle, as the codebase becomes harder to work with, which leads to more pressure, **lowered morale** and even more technical debt. This is why it's important to spot the symptoms early on and take action to reduce it.

## Solutions

Dealing with technical debt is not always easy, but it's not impossible either. The most important step is realizing that it exists and that it affects the team's productivity. Once that is done, the team can start to take action. Solutions vary, depending on the team, industry and nature of the technical debt itself.

Broadly speaking, it's acceptable to **take on debt early on** to get the project off the ground. Identifying and keeping track of it ensures that the team knows where to start when the time comes to start dealing with it. This also helps figure out how much of an impact it has and how to prioritize maintenance tasks.

As soon as the project is out of the door, the team should **start to reduce debt** or at least not take on any more. Prioritization can be based either on the impact or the effort required for each refactor. A combination of two can also be beneficial, as it can allow small maintenance tasks to fill in the gaps, while more impactful refactors are planned and coordinated accordingly.

After getting to a manageable level, it's important to **keep technical debt in check**. Here's where having a process in place can work wonders. By knowing how to keep debt in check, when to take action and continuously working towards that goal, the team can ensure that the codebase is always in a good state.

## Conclusion

Technical debt is a natural, unavoidable part of software development. Miscommunication and lack of understanding can lead to it, resulting in lowered velocity and morale. Luckily, a bit of awareness, good communication and an actionable process can help reduce it and keep it in check.

---

### Snippets Articles S Terminal Shortcuts Cheatsheet (`content/snippets/articles/s/terminal-shortcuts-cheatsheet.md`)

---
title: Bash Shortcuts Cheat Sheet
tags: [webdev,bash,cheatsheet]
cover: terminal
excerpt: Maximize your productivity when working with a bash or other shell terminal by using these keyboard shortcuts.
listed: true
dateModified: 2021-06-12
---

- <kbd>Ctrl</kbd> + <kbd>C</kbd> Terminate the command
- <kbd>Ctrl</kbd> + <kbd>Z</kbd> Suspend the command
- <kbd>Ctrl</kbd> + <kbd>A</kbd> Move to the start of the line
- <kbd>Ctrl</kbd> + <kbd>E</kbd> Move to the end of the line
- <kbd>Ctrl</kbd> + <kbd>F</kbd> Move forward one character
- <kbd>Ctrl</kbd> + <kbd>B</kbd> Move backward one character
- <kbd>Ctrl</kbd> + <kbd>U</kbd> Delete from the cursor to the start of the line
- <kbd>Ctrl</kbd> + <kbd>K</kbd> Delete from the cursor to the end of the line
- <kbd>Ctrl</kbd> + <kbd>W</kbd> Delete from the cursor to the start of the word
- <kbd>Ctrl</kbd> + <kbd>D</kbd> Delete the character under the cursor
- <kbd>Ctrl</kbd> + <kbd>H</kbd> Delete the character before the cursor
- <kbd>Ctrl</kbd> + <kbd>T</kbd> Swap the character under the cursor with the previous one
- <kbd>Ctrl</kbd> + <kbd>L</kbd> Clear the screen

---

### Snippets Articles S The Frontend Trap (`content/snippets/articles/s/the-frontend-trap.md`)

---
title: The frontend trap
tags: [webdev,career,programming,jobs]
cover: coffee-n-laptop-2
excerpt: How staying on the frontend can limit your career growth and a hot take on how to escape it.
listed: true
dateModified: 2025-08-19
---

A while back, I was having a conversation with a mid-senior colleague I'm mentoring about their career. In the course of our discussion, my decision to expand my skillset from frontend to full-stack development came up. They were surprised and asked me why I would do that, given that frontend is fun and I'm pretty good at it, having more experience than on the backend. What I couldn't elaborate a couple of years back, when I made said decision, I could now explain in detail.

## The limits of frontend work

Starting with something obvious to me, I mentioned that **some high-level problems simply don't exist on the frontend**. You rarely worry about distributed systems, data consistency, or scaling a service to millions of users. Most frontend work revolves around user interfaces, state management, and **making things look and feel right**. That's important, but it's only one piece of the puzzle.

Frontend development time is often spent handling edge cases, wrestling with browser quirks, or trying to turn every problem into a React-shaped nail. You might spend hours fiddling with CSS, chasing pixel perfection, or patching compatibility issues so users can access your app on every device. It's a **grind**, and it's easy to get stuck in a loop of **solving the same types of problems over and over**.

## How the backend is different

Backend development throws you into the deep end. You face **scaling, performance bottlenecks, design patterns, and architecture decisions** that shape entire systems. Optimizing algorithms, picking the right data structures, and making calls that impact reliability and speed are daily challenges. The stakes are higher, and the problems are broader.

You also get hands-on with the technologies that power modern web applications. **Databases, containers, message queues** don't seem like buzzwords anymore. They're the backbone of most serious tech stacks. Knowing how they work makes you valuable in almost any job.

All the deep **computer science concepts** you learned in college finally come into play. On the backend, you'll actually use **algorithms, data structures, and design patterns** that might have seemed academic before. Concepts like **concurrency, caching, and distributed algorithms** become part of your toolkit. This is a stark contrast to frontend work, where you can go years without touching any of that.


## Growing beyond the frontend

If you want to become a truly **senior developer**, with advanced knowledge and a deep understanding of **systems, patterns, and code**, you need to invest in backend development after you've mastered the frontend. That's where you'll find the **challenges** that push you to grow.

A more **varied skillset**, what we often call a **T-shaped developer profile**, makes you more valuable to companies. You can adapt to different situations, jump into new projects, and handle whatever comes your way. You're not just the _React person_ or the _CSS wizard._ You're someone who can see the big picture and **contribute at every level**.

## Stay curious, stay relevant

In conclusion, what was my answer? That it's better to **diversify your skills** than to go super deep into just frontend development. The landscape changes fast. What's relevant today might be legacy knowledge tomorrow. Remember CSS floats? IE compatibility hacks? jQuery? Ok, that last one might still be a little relevant. But the rest? They were essential once, but now they're mostly footnotes nowadays.

It's easy to stagnate by doing the same thing for years, even if you become a master at it. Normalize _good enough_ and **stay curious**. Try new things, join more conversations, and adapt as technologies shift. That's how you **stay relevant**, energized, and ready for whatever comes next.

---

### Snippets Articles S Virtues Of Pair Programming (`content/snippets/articles/s/virtues-of-pair-programming.md`)

---
title: The virtues of pair programming
tags: [webdev,career,programming,jobs]
cover: coffee-table-setup
excerpt: Pair programming can be valuable for junior and senior developers alike, fostering collaboration and knowledge sharing.
listed: true
dateModified: 2025-09-20
---

I've got to admit, we did not expect **pair programming** to reshape the way we work initially. But, after months of trying it with my team, the impact is obvious.

Tackling **difficult problems** feels less daunting. We **ask for help** earlier, not as a last resort. The way we **analyze and explain** things is sharper, clearer. Each session brings a new perspective, and sometimes, a breakthrough.

If you're a non-believer, like I was, let me share some insights!

## Mindset alignment

For senior engineers, pair programming is a window into the **beginner's mindset**. You see what trips up junior developers, understand their needs, their questions, and the gaps in their knowledge. This makes it easier to mentor, guide, and build trust. Sometimes, you **rediscover the basics** and realize how much you take for granted.

Junior engineers benefit even more. Growth happens fast when you **share the process** of implementation, debugging, and coding. You learn not just what to do, but why. You see how others think, how they break down problems, and how they recover from mistakes. Every session is a lesson in real-world development.

## Growing together

For junior developers, pair programming is **like rubber duck debugging**, except the duck talks back. You get feedback, suggestions, and new ways to solve the issue at hand. It's a **conversation**, not a monologue. You are never alone with a problem.

Senior developers find **opportunities to dive deeper** into interesting topics. You get to share your wisdom, shape the mentality of new team members, and keep your own skills sharp. Sometimes, you learn something new from a **fresh perspective**.

## Team dynamics

The benefits go beyond code. Pair programming builds **strong interpersonal relationships**. It strengthens team bonds and creates a sense of **shared responsibility**. When two people work on a problem, understanding is shared. This makes future collaboration and maintenance easier.

Pair programming creates a **safe space**. The simplest questions and personal difficulties can be resolved without judgment. You get **practical examples**, not just theory. It is easier to admit when you are stuck, and easier to ask for help. The team learns that **asking for help early** is not a waste of time. It leads to better outcomes for everyone.

Cross-team or cross-function pair programming is even more powerful. It minimizes blind spots, demystifies black boxes, and promotes **knowledge sharing across teams**. You learn how other teams work, and you bring back new ideas.

## Closing thoughts

Some worry that pair programming takes twice the time. In reality, it is often **faster** than having someone struggle alone, then ask for help, and repeat the cycle. Brainstorming and working as a team can be more efficient. Sometimes, a tough problem is the perfect excuse for more than two people to jump on a call and solve it together.

Pair programming is not just a technique. It is a **mindset**. It's about learning, sharing, and building better teams. You should try it sometime. You might be surprised by what you discover!

---

### Snippets Css S 10 Css Background Patterns (`content/snippets/css/s/10-css-background-patterns.md`)

---
title: 10 beautiful CSS background patterns
shortTitle: CSS background patterns
language: css
tags: [visual,cheatsheet]
cover: armchair-design
excerpt: I hand picked 10 of my favorite CSS background patterns from MagicPattern for your next project. Get them now!
listed: true
dateModified: 2024-05-09
---

[MagicPattern](https://www.magicpattern.design/tools/css-backgrounds/) has an amazing collection of beautiful pure CSS background pattern to spice up your designs. I highly recommend checking out the full collection. Meantime, here are my top picks to get you started:

<style>
.pattern-box {
  grid-column: 1 / -1;
  width: 100%;
  height: 144px;
  border-radius: var(--layout-border-radius) var(--layout-border-radius) 0 0;
}

.pattern-box + pre {
  margin-block-start: calc(-1 * var(--layout-row-spacing, 0));
  border-radius: 0 0 var(--layout-border-radius) var(--layout-border-radius);
}

.pattern-box + pre::before {
  opacity: 0;
}

main > article > h2 {
  margin: 0;
  font-size: var(--font-sm);
  transform: translateY(186px);
  z-index: 1;
  height: 0;
}

main > article > h2::after {
  display: inline-block;
  font-size: var(--font-sm);
  content: "(CSS)";
  margin-inline-start: var(--spacing-2);
}

.lines-2 {
  background-color: #fefefe;
  background-image: linear-gradient(to right, #5394fd, #5394fd 12px, #fefefe 12px, #fefefe );
  background-size: 24px 100%;
}

.zigzag {
  background-color: #fefefe;
  background-image:  linear-gradient(135deg, #5394fd 25%, transparent 25%), linear-gradient(225deg, #5394fd 25%, transparent 25%), linear-gradient(45deg, #5394fd 25%, transparent 25%), linear-gradient(315deg, #5394fd 25%, #fefefe 25%);
  background-position:  24px 0, 24px 0, 0 0, 0 0;
  background-size: 48px 48px;
  background-repeat: repeat;
}

.polka {
  background-color: #fefefe;
  background-image: radial-gradient(#5394fd 1.2px, #fefefe 1.2px);
  background-size: 24px 24px;
}

.rectangles {
  background-color: #fefefe;
  background-image:  repeating-linear-gradient(45deg, #5394fd 25%, transparent 25%, transparent 75%, #5394fd 75%, #5394fd), repeating-linear-gradient(45deg, #5394fd 25%, #fefefe 25%, #fefefe 75%, #5394fd 75%, #5394fd);
  background-position: 0 0, 24px 24px;
  background-size: 48px 48px;
}

.rhombus {
  background-color: #fefefe;
  background-image:  linear-gradient(135deg, #5394fd 25%, transparent 25%), linear-gradient(225deg, #5394fd 25%, transparent 25%), linear-gradient(45deg, #5394fd 25%, transparent 25%), linear-gradient(315deg, #5394fd 25%, #fefefe 25%);
  background-position:  24px 0, 24px 0, 0 0, 0 0;
  background-size: 24px 24px;
  background-repeat: repeat;
}

.paper {
  background-color: #fefefe;
  background-image:  linear-gradient(#5394fd 4.8px, transparent 4.8px), linear-gradient(90deg, #5394fd 4.8px, transparent 4.8px), linear-gradient(#5394fd 2.4px, transparent 2.4px), linear-gradient(90deg, #5394fd 2.4px, #fefefe 2.4px);
  background-size: 120px 120px, 120px 120px, 24px 24px, 24px 24px;
  background-position: -4.8px -4.8px, -4.8px -4.8px, -2.4px -2.4px, -2.4px -2.4px;
}

.diagonal {
  background-color: #fefefe;
  background: repeating-linear-gradient( 45deg, #5394fd, #5394fd 12px, #fefefe 12px, #fefefe 60px );
}

.triangle {
  background-color: #fefefe;
  background-image: linear-gradient(45deg, #5394fd 50%, #fefefe 50%);
  background-size: 24px 24px;
}

.wavy {
  background-color: #fefefe;
  background-image: repeating-radial-gradient( circle at 0 0, transparent 0, #fefefe 24px ), repeating-linear-gradient( #5394fd55, #5394fd );
}

.isometric {
  background-color: #fefefe;
  background-image:  linear-gradient(30deg, #5394fd 12%, transparent 12.5%, transparent 87%, #5394fd 87.5%, #5394fd), linear-gradient(150deg, #5394fd 12%, transparent 12.5%, transparent 87%, #5394fd 87.5%, #5394fd), linear-gradient(30deg, #5394fd 12%, transparent 12.5%, transparent 87%, #5394fd 87.5%, #5394fd), linear-gradient(150deg, #5394fd 12%, transparent 12.5%, transparent 87%, #5394fd 87.5%, #5394fd), linear-gradient(60deg, #5394fd77 25%, transparent 25.5%, transparent 75%, #5394fd77 75%, #5394fd77), linear-gradient(60deg, #5394fd77 25%, transparent 25.5%, transparent 75%, #5394fd77 75%, #5394fd77);
  background-size: 48px 84px;
  background-position: 0 0, 0 0, 24px 42px, 24px 42px, 0 0, 24px 42px;
}
</style>

## Stripes pattern

<div class="pattern-box lines-2"></div>

```css
.lines-2 {
  background-color: #fefefe;
  background-image: linear-gradient(
    to right,
    #5394fd,
    #5394fd 12px,
    #fefefe 12px,
    #fefefe
  );
  background-size: 24px 100%;
}
```

## Zig zag pattern

<div class="pattern-box zigzag"></div>

```css
.zigzag {
  background-color: #fefefe;
  background-image: linear-gradient(135deg, #5394fd 25%, transparent 25%),
    linear-gradient(225deg, #5394fd 25%, transparent 25%),
    linear-gradient(45deg, #5394fd 25%, transparent 25%),
    linear-gradient(315deg, #5394fd 25%, #fefefe 25%);
  background-position:
    24px 0,
    24px 0,
    0 0,
    0 0;
  background-size: 48px 48px;
  background-repeat: repeat;
}
```

## Polka dot pattern

<div class="pattern-box polka"></div>

```css
.polka {
  background-color: #fefefe;
  background-image: radial-gradient(#5394fd 1.2px, #fefefe 1.2px);
  background-size: 24px 24px;
}
```

## Checkerboard pattern

<div class="pattern-box rectangles"></div>

```css
.rectangles {
  background-color: #fefefe;
  background-image: repeating-linear-gradient(
      45deg,
      #5394fd 25%,
      transparent 25%,
      transparent 75%,
      #5394fd 75%,
      #5394fd
    ),
    repeating-linear-gradient(
      45deg,
      #5394fd 25%,
      #fefefe 25%,
      #fefefe 75%,
      #5394fd 75%,
      #5394fd
    );
  background-position:
    0 0,
    24px 24px;
  background-size: 48px 48px;
}
```

## Rhombus pattern

<div class="pattern-box rhombus"></div>

```css
.rhombus {
  background-color: #fefefe;
  background-image: linear-gradient(135deg, #5394fd 25%, transparent 25%),
    linear-gradient(225deg, #5394fd 25%, transparent 25%),
    linear-gradient(45deg, #5394fd 25%, transparent 25%),
    linear-gradient(315deg, #5394fd 25%, #fefefe 25%);
  background-position:
    24px 0,
    24px 0,
    0 0,
    0 0;
  background-size: 24px 24px;
  background-repeat: repeat;
}
```

## Paper pattern

<div class="pattern-box paper"></div>

```css
.paper {
  background-color: #fefefe;
  background-image: linear-gradient(#5394fd 4.8px, transparent 4.8px),
    linear-gradient(90deg, #5394fd 4.8px, transparent 4.8px),
    linear-gradient(#5394fd 2.4px, transparent 2.4px),
    linear-gradient(90deg, #5394fd 2.4px, #fefefe 2.4px);
  background-size:
    120px 120px,
    120px 120px,
    24px 24px,
    24px 24px;
  background-position:
    -4.8px -4.8px,
    -4.8px -4.8px,
    -2.4px -2.4px,
    -2.4px -2.4px;
}
```

## Diagonal pattern

<div class="pattern-box diagonal"></div>

```css
.diagonal {
  background-color: #fefefe;
  background: repeating-linear-gradient(
    45deg,
    #5394fd,
    #5394fd 12px,
    #fefefe 12px,
    #fefefe 60px
  );
}
```

## Triangles pattern

<div class="pattern-box triangle"></div>

```css
.triangle {
  background-color: #fefefe;
  background-image: linear-gradient(45deg, #5394fd 50%, #fefefe 50%);
  background-size: 24px 24px;
}
```

## Waves pattern

<div class="pattern-box wavy"></div>

```css
.wavy {
  background-color: #fefefe;
  background-image: repeating-radial-gradient(
      circle at 0 0,
      transparent 0,
      #fefefe 24px
    ),
    repeating-linear-gradient(#5394fd55, #5394fd);
}
```

## Isometric pattern

<div class="pattern-box isometric"></div>

```css
.isometric {
  background-color: #fefefe;
  background-image: linear-gradient(
      30deg,
      #5394fd 12%,
      transparent 12.5%,
      transparent 87%,
      #5394fd 87.5%,
      #5394fd
    ),
    linear-gradient(
      150deg,
      #5394fd 12%,
      transparent 12.5%,
      transparent 87%,
      #5394fd 87.5%,
      #5394fd
    ),
    linear-gradient(
      30deg,
      #5394fd 12%,
      transparent 12.5%,
      transparent 87%,
      #5394fd 87.5%,
      #5394fd
    ),
    linear-gradient(
      150deg,
      #5394fd 12%,
      transparent 12.5%,
      transparent 87%,
      #5394fd 87.5%,
      #5394fd
    ),
    linear-gradient(
      60deg,
      #5394fd77 25%,
      transparent 25.5%,
      transparent 75%,
      #5394fd77 75%,
      #5394fd77
    ),
    linear-gradient(
      60deg,
      #5394fd77 25%,
      transparent 25.5%,
      transparent 75%,
      #5394fd77 75%,
      #5394fd77
    );
  background-size: 48px 84px;
  background-position:
    0 0,
    0 0,
    24px 42px,
    24px 42px,
    0 0,
    24px 42px;
}
```

---

### Snippets Css S 25 Css Gradients (`content/snippets/css/s/25-css-gradients.md`)

---
title: 25 CSS gradients for your next project
shortTitle: CSS gradients
language: css
tags: [visual,cheatsheet]
cover: colors-mural
excerpt: I hand picked 25 of my favorite CSS gradients from uiGradients for your next design. Get them now!
listed: true
dateModified: 2024-01-12
---

[uiGradients](https://uigradients.com/) has an amazing collection of ready-to-use CSS gradients for pretty much anything. I highly recommend checking out the full collection. Meantime, here are my top picks in case you're looking for some color:

<style>
.gradient-box {
  grid-column: 1 / -1;
  width: 100%;
  height: 144px;
  border-radius: var(--layout-border-radius) var(--layout-border-radius) 0 0;
  margin-top: var(--layout-row-spacing);
}

.gradient-box + pre {
  margin-block-start: calc(-1 * var(--layout-row-spacing, 0));
  border-radius: 0 0 var(--layout-border-radius) var(--layout-border-radius);
}

.stripe {
  background: linear-gradient(to right, #1fa2ff, #12d8fa, #a6ffcb);
}
.flare {
  background: linear-gradient(to right, #f12711, #f5af19);
}
.vanusa {
  background: linear-gradient(to right, #da4453, #89216b);
}
.sublime-light {
  background: linear-gradient(to right, #fc5c7d, #6a82fb);
}
.bighead {
  background: linear-gradient(to right, #c94b4b, #4b134f);
}
.velvet-sun {
  background: linear-gradient(to right, #e1eec3, #f05053);
}
.relay {
  background: linear-gradient(to right, #3a1c71, #d76d77, #ffaf7b);
}
.crystal-clear {
  background: linear-gradient(to right, #159957, #155799);
}
.celestial {
  background: linear-gradient(to right, #c33764, #1d2671);
}
.ibiza-sunset {
  background: linear-gradient(to right, #ee0979, #ff6a00);
}
.fresh-turboscent {
  background: linear-gradient(to right, #f1f2b5, #135058);
}
.cheer-up-emo-kid {
  background: linear-gradient(to right, #556270, #ff6b6b);
}
.starfall {
  background: linear-gradient(to right, #f0c27b, #4b1248);
}
.nelson {
  background: linear-gradient(to right, #f2709c, #ff9472);
}
.forever-lost {
  background: linear-gradient(to right, #5d4157, #a8caba);
}
.blurry-beach {
  background: linear-gradient(to right, #d53369, #cbad6d);
}
.influenza {
  background: linear-gradient(to right, #c04848, #480048);
}
.calm-darya {
  background: linear-gradient(to right, #5f2c82, #49a09d);
}
.titanium {
  background: linear-gradient(to right, #283048, #859398);
}
.pinky {
  background: linear-gradient(to right, #dd5e89, #f7bb97);
}
.purple-paradise {
  background: linear-gradient(to right, #1d2b64, #f8cdda);
}
.horizon {
  background: linear-gradient(to right, #003973, #e5e5be);
}
.noon-to-dusk {
  background: linear-gradient(to right, #ff6e7f, #bfe9ff);
}
.jshine {
  background: linear-gradient(to right, #12c2e9, #c471ed, #f64f59);
}
.argon {
  background: linear-gradient(to right, #03001e, #7303c0, #ec38bc, #fdeff9);
}
</style>

<div class="gradient-box stripe"></div>

```css
.stripe {
  background: linear-gradient(to right, #1fa2ff, #12d8fa, #a6ffcb);
}
```

<div class="gradient-box flare"></div>

```css
.flare {
  background: linear-gradient(to right, #f12711, #f5af19);
}
```

<div class="gradient-box vanusa"></div>

```css
.vanusa {
  background: linear-gradient(to right, #da4453, #89216b);
}
```

<div class="gradient-box sublime-light"></div>

```css
.sublime-light {
  background: linear-gradient(to right, #fc5c7d, #6a82fb);
}
```

<div class="gradient-box bighead"></div>

```css
.bighead {
  background: linear-gradient(to right, #c94b4b, #4b134f);
}
```

<div class="gradient-box velvet-sun"></div>

```css
.velvet-sun {
  background: linear-gradient(to right, #e1eec3, #f05053);
}
```

<div class="gradient-box argon"></div>

```css
.argon {
  background: linear-gradient(to right, #03001e, #7303c0, #ec38bc, #fdeff9);
}
```

<div class="gradient-box celestial"></div>

```css
.celestial {
  background: linear-gradient(to right, #c33764, #1d2671);
}
```

<div class="gradient-box relay"></div>

```css
.relay {
  background: linear-gradient(to right, #3a1c71, #d76d77, #ffaf7b);
}
```

<div class="gradient-box crystal-clear"></div>

```css
.crystal-clear {
  background: linear-gradient(to right, #159957, #155799);
}
```

<div class="gradient-box ibiza-sunset"></div>

```css
.ibiza-sunset {
  background: linear-gradient(to right, #ee0979, #ff6a00);
}
```

<div class="gradient-box fresh-turboscent"></div>

```css
.fresh-turboscent {
  background: linear-gradient(to right, #f1f2b5, #135058);
}
```

<div class="gradient-box cheer-up-emo-kid"></div>

```css
.cheer-up-emo-kid {
  background: linear-gradient(to right, #556270, #ff6b6b);
}
```

<div class="gradient-box starfall"></div>

```css
.starfall {
  background: linear-gradient(to right, #f0c27b, #4b1248);
}
```

<div class="gradient-box nelson"></div>

```css
.nelson {
  background: linear-gradient(to right, #f2709c, #ff9472);
}
```

<div class="gradient-box forever-lost"></div>

```css
.forever-lost {
  background: linear-gradient(to right, #5d4157, #a8caba);
}
```

<div class="gradient-box blurry-beach"></div>

```css
.blurry-beach {
  background: linear-gradient(to right, #d53369, #cbad6d);
}
```

<div class="gradient-box influenza"></div>

```css
.influenza {
  background: linear-gradient(to right, #c04848, #480048);
}
```

<div class="gradient-box jshine"></div>

```css
.jshine {
  background: linear-gradient(to right, #12c2e9, #c471ed, #f64f59);
}
```

<div class="gradient-box calm-darya"></div>

```css
.calm-darya {
  background: linear-gradient(to right, #5f2c82, #49a09d);
}
```

<div class="gradient-box titanium"></div>

```css
.titanium {
  background: linear-gradient(to right, #283048, #859398);
}
```

<div class="gradient-box pinky"></div>

```css
.pinky {
  background: linear-gradient(to right, #dd5e89, #f7bb97);
}
```

<div class="gradient-box purple-paradise"></div>

```css
.purple-paradise {
  background: linear-gradient(to right, #1d2b64, #f8cdda);
}
```

<div class="gradient-box horizon"></div>

```css
.horizon {
  background: linear-gradient(to right, #003973, #e5e5be);
}
```

<div class="gradient-box noon-to-dusk"></div>

```css
.noon-to-dusk {
  background: linear-gradient(to right, #ff6e7f, #bfe9ff);
}
```

---

### Snippets Css S Alternating Text (`content/snippets/css/s/alternating-text.md`)

---
title: Alternating text animations
language: css
tags: [animation]
cover: italian-horizon
excerpt: Ever wondered how those alternating text animations work? Here's a simple way to create one using CSS and JavaScript.
listed: true
dateModified: 2024-09-12
---

Alternating text animations are a great way to add some playfulness to your website. They can be used to display different words or phrases in a loop, often used to showcase a unique selling point or a list of features.

Luckily, with a little CSS and JavaScript, you can create your own. Starting with the CSS, you need an element to display the content and a simple `animation` to make the text disappear.

Then, in JavaScript, you can define an array of the different words or phrases you want to alternate between and use the first one to initialize the content. By using `EventTarget.addEventListener()` to listen for the `'animationiteration'` event, you can update the content of the element to the next word in the array each time the animation completes an iteration.

```css
.alternating {
  animation-name: alternating-text;
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: ease;
}

@keyframes alternating-text {
  90% {
    display: none;
  }
}
```

```js
const texts = ['Java', 'Python', 'C', 'C++', 'C#', 'Javascript'];
const element = document.querySelector('.alternating');

let i = 0;
const listener = e => {
  i = i < texts.length - 1 ? i + 1 : 0;
  element.innerHTML = texts[i];
};

element.innerHTML = texts[0];
element.addEventListener('animationiteration', listener, false);
```

https://codepen.io/chalarangelo/pen/MWNybVQ

> [!CAUTION]
>
> This implementation is **not accessible to screen readers**. If you want to make it accessible, consider using ARIA attributes or other techniques to ensure that the content is still readable by all users.

---

### Snippets Css S Box Sizing Reset (`content/snippets/css/s/box-sizing-reset.md`)

---
title: Box-sizing reset
language: css
tags: [layout]
cover: interior
excerpt: Reset the box-model so that `width` and `height` are not affected by `border` or `padding`.
listed: true
dateModified: 2024-09-01
---

If you're not familiar with the **box-model**, it's a way to calculate the size of an element. The box-model consists of the `content`, `padding`, `border`, and `margin` properties.

By default, the `width` and `height` of an element are calculated based on the `content` property only. This means that if you add `padding` or `border` to an element, its `width` and `height` will increase.

But what if you want to include the `padding` and `border` in the `width` and `height` calculations? That's where `box-sizing` comes in. Using `box-sizing: border-box`, you can include the `padding` and `border` in the `width` and `height` calculations. You can also use `box-sizing: inherit` to pass down the `box-sizing` property from parent to child elements.

```html
<div class="box">border-box</div>
<div class="box content-box">content-box</div>
```

```css
/* Reset the box-model */
div {
  box-sizing: border-box;
}

/* Pass down the box-sizing property */
*,
*::before,
*::after {
  box-sizing: inherit;
}

/* Example usage */
.box {
  display: inline-block;
  width: 120px;
  height: 120px;
  padding: 8px;
  margin: 8px;
  background: #F24333;
  color: white;
  border: 1px solid #BA1B1D;
  border-radius: 4px;
}

/* Override the box-sizing property */
.content-box {
  box-sizing: content-box;
}
```

---

### Snippets Css S Broken Image Fallback (`content/snippets/css/s/broken-image-fallback.md`)

---
title: Display a fallback for images that fail to load
shortTitle: Broken image fallback
language: css
tags: [visual]
cover: building-facade
excerpt: Having trouble loading an image? Display a fallback message instead!
listed: true
dateModified: 2024-09-09
---

Ever wanted to display a message when an image fails to load? Perhaps you want to show the image's URL or a custom error message. Luckily, there's an easy way to do so.

The key to this technique lies in the way we handle the `<img>` element. Instead of treating it as an image container, we'll **treat it as a text container**. This allows us to apply styles and pseudo-elements to it, which will only be displayed when the image fails to load.

Using the `::before` and `::after` pseudo-elements, we can display an error message and the image's URL. This way, users will know what went wrong and where the image was supposed to be.

The best part? This fallback message is **only displayed when the image fails to load**, so it won't interfere with the image itself.

```html
<img src="/nowhere/to/be/found.jpg" />
```

```css
img {
  display: block;
  font-family: sans-serif;
  font-weight: 300;
  height: auto;
  line-height: 2;
  position: relative;
  text-align: center;
  width: 100%;
}

img::before {
  content: "Sorry, this image is unavailable.";
  display: block;
  margin-bottom: 8px;
}

img::after {
  content: "(url: " attr(src) ")";
  display: block;
  font-size: 12px;
}
```

https://codepen.io/chalarangelo/pen/yLmOeVN

> [!NOTE]
>
> This fallback may require **additional accessibility considerations**. Make sure to test it with a screen reader to ensure it's accessible to all users. Also, always provide a meaningful `alt` attribute for your images to help users understand their content.

---

### Snippets Css S Button Hover Transitions (`content/snippets/css/s/button-hover-transitions.md`)

---
title: CSS Button Transitions
shortTitle: Button Transitions
language: css
tags: [animation]
cover: painters-desk
excerpt: Add some life to your buttons with these simple CSS transitions.
listed: true
dateModified: 2024-08-29
---

CSS transitions are a great way to add some life to your buttons. From growing and shrinking, to fill, swing and border animations, there are many ways to make your buttons more interactive.

https://codepen.io/chalarangelo/pen/qBedyER

## Button grow animation

To create a **grow animation** on hover, you can use an appropriate `transition` to animate changes to the element. Use the `:hover` pseudo-class to change the `transform` to `scale(1.1)`, growing the element when the user hovers over it.

> [!TIP]
>
> You can also use the new `scale` property to scale the element to a specific size. Simply replace `transform: scale(1.1)` with `scale: 110%`.

```css
.button-grow {
  transition: all 0.3s ease-in-out;
}

.button-grow:hover {
  transform: scale(1.1);
}
```

## Button shrink animation

Similarly, you can create a **shrink animation** on hover by using an appropriate `transition` to animate changes to the element. Use the `:hover` pseudo-class to change the `transform` to `scale(0.8)`, shrinking the element when the user hovers over it.

> [!TIP]
>
> Again, you can use the new `scale` property to scale the element to a specific size. Simply replace `transform: scale(0.8)` with `scale: 80%`.

```css
.button-shrink {
  transition: all 0.3s ease-in-out;
}

.button-shrink:hover {
  transform: scale(0.8);
}
```

## Button fill animation

To create a **fill animation** on hover, set a `color` and `background` and use an appropriate `transition` to animate changes to the element. Use the `:hover` pseudo-class to change the `background` and `color` of the element when the user hovers over it.

```css
.button-fill {
  background: #fff;
  color: #000;
  transition: all 0.3s ease-in-out;
}

.button-fill:hover {
  background: #000;
  color: #fff;
}
```

## Button swing animation

For a **swing effect**, you can create an animation, using `@keyframes` and an appropriate `transition` to animate changes to the element. Use the `:hover` pseudo-class to apply an `animation` that uses `transform` to make the element swing.

```css
.button-swing {
  transition: all 0.2s ease-in-out;
}

.button-swing:hover {
  animation: swing 1s ease;
  animation-iteration-count: 1;
}

@keyframes swing {
  15% {
    transform: translateX(5px);
  }
  30% {
    transform: translateX(-5px);
  }
  50% {
    transform: translateX(3px);
  }
  65% {
    transform: translateX(-3px);
  }
  80% {
    transform: translateX(2px);
  }
  100% {
    transform: translateX(0);
  }
}
```

## Button border animation

Finally, for a **border animation**, you can create a border that changes color on hover. For this effect, you'll need to use the `::before` and `::after` pseudo-elements to create two boxes opposite each other above and below the box. Then, using the `:hover` pseudo-class, you can extend the `width` of those elements to `100%` on hover and animate the change using `transition`.

```css
.button-bordered {
  border: none;
  outline: none;
  position: relative;
}

.button-bordered::before,
.button-bordered::after {
  border: 0 solid transparent;
  transition: all 0.3s;
  content: '';
  height: 0;
  position: absolute;
  width: 24px;
}

.button-bordered::before {
  border-top: 2px solid #263059;
  right: 0;
  top: -4px;
}

.button-bordered::after {
  border-bottom: 2px solid #263059;
  bottom: -4px;
  left: 0;
}

.button-bordered:hover::before,
.button-bordered:hover::after {
  width: 100%;
}
```

---

### Snippets Css S Card Hover Effects (`content/snippets/css/s/card-hover-effects.md`)

---
title: Card hover effects
language: css
tags: [animation]
cover: clouds-n-mountains
excerpt: Create cards with hover effects, such as rotating, shifting and perspective transforms.
listed: true
dateModified: 2024-09-11
---

Cards, one of the most common layout elements in modern web design, provide a ton of opportunities for creative hover effects. Here are a few examples of card hover effects that you can use to make your website more interactive and engaging.

https://codepen.io/chalarangelo/pen/NWQNxQG


## Rotating card

To create a **two-sided card** that rotates on hover, you first need a container element with two child elements, one for the front side and one for the back side of the card. You can then use the `rotateY()` function to rotate the card around the Y-axis, and the `backface-visibility` property to hide the back side of the card when it is not visible.

```html
<div class="rotating-card">
  <div class="card-side front"></div>
  <div class="card-side back"></div>
</div>
```

```css
.rotating-card {
  perspective: 150rem;
  position: relative;
  box-shadow: none;
  background: none;
}

.rotating-card .card-side {
  transition: all 0.8s ease;
  backface-visibility: hidden;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.rotating-card .card-side.back {
  transform: rotateY(-180deg);
}

.rotating-card:hover .card-side.front {
  transform: rotateY(180deg);
}

.rotating-card:hover .card-side.back {
  transform: rotateY(0deg);
}
```

## Shifting card

For a shifting card, you will need to leverage [CSS variables](/css/s/variables) and a little bit of JavaScript to track the **position of the mouse cursor** and adjust the card's position accordingly. You'll use the `mousemove` event to track the cursor's position and calculate the relative distance between the cursor and the center of the card, using `Element.getBoundingClientRect()` to get the card's position and dimensions.

Then, using the CSS variables, you can apply a `transform` property to the card element that shifts it based on the cursor's position. To make the change smoother, use the `transition` property to animate the transformation.

```css
.shifting-card {
  transition: transform 0.2s ease-out;
  transform: rotateX(calc(10deg * var(--dx, 0)))
    rotateY(calc(10deg * var(--dy, 0)));
}
```

```js
const card = document.querySelector('.shifting-card');
const { x, y, width, height } = card.getBoundingClientRect();
const cx = x + width / 2;
const cy = y + height / 2;

const handleMove = e => {
  const { pageX, pageY } = e;
  const dx = (cx - pageX) / (width / 2);
  const dy = (cy - pageY) / (height / 2);
  e.target.style.setProperty('--dx', dx);
  e.target.style.setProperty('--dy', dy);
};

card.addEventListener('mousemove', handleMove);
```

## Perspective card

Finally, for the perspective card, you will only need a `transform` with a `perspective()` function and a `rotateY()` function to create the perspective effect. The `transition` property will animate the `transform` attribute on hover.

```css
.perspective-card {
  transform: perspective(1500px) rotateY(15deg);
  transition: transform 1s ease 0s;
}

.perspective-card:hover {
  transform: perspective(3000px) rotateY(5deg);
}
```

---

### Snippets Css S Card Image Cutout (`content/snippets/css/s/card-image-cutout.md`)

---
title: Card with image cutout
language: css
tags: [visual]
cover: radio-monstera
excerpt: Cutout effects seem tricky to implement, but they really aren't. CSS has you covered!
listed: true
dateModified: 2024-09-13
---

Cutout effects are a great way to add visual interest to your website. They can be used to create unique layouts, highlight important content, or simply make your website more visually appealing. One common way to create a cutout effect is to use an image cutout, where an image is displayed inside a container with a cutout shape.

To create this effect, you'll need a **container element** with a colored `background` and a **card element** containing the image you want to display. You can then use the `::before` pseudo-element to add a `border` around the image, matching the container element's `background` and creating the illusion of a cutout in the card.

https://codepen.io/chalarangelo/pen/ZEgWBRo

```html
<div class="container">
  <div class="card">
    <figure>
      <img src="/path/to/img.jpg"/>
    </figure>
    <p class="content">Card content</p>
  </div>
</div>
```

```css
.container {
  background: #f3f1fe;
}

.card {
  background: #fff;
  box-shadow: 0 0 5px -2px rgba(0, 0, 0, 0.1);
}

.card figure {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  margin-top: -60px;
  position: relative;
}

.card figure::before {
  content: "";
  border-radius: inherit;
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: 100%;
  transform: translate(-50%, -50%);
  border: 1rem solid #f3f1fe;
  box-shadow: 0 1px rgba(0, 0, 0, 0.1);
}

.card figure img {
  border-radius: inherit;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
```

---

### Snippets Css S Centering (`content/snippets/css/s/centering.md`)

---
title: Centering content with CSS
language: css
tags: [layout]
cover: malibu
excerpt: Centering content with CSS might often feel difficult. Here are 4 easy ways you can do it.
listed: true
dateModified: 2024-05-07
---

_How to center a div_ has become a little bit of a joke in the web development community, and for good reason. Not only is it a common task, but it can also be a bit tricky to get right, especially when you're new to CSS. Luckily, modern CSS solutions exist for pretty much any scenario you might encounter.

## Flexbox centering

Using **flexbox** to vertically and horizontally center content is usually the **preferred method**. All it takes is three lines of code in the container element to set `display: flex` and then center the child element vertically and horizontally using `align-items: center` and `justify-content: center` respectively.

```html
<div class="flexbox-centering">
  <div class="content">Content</div>
</div>
```

```css
.flexbox-centering {
  display: flex;
  justify-content: center;
  align-items: center;
}
```

https://codepen.io/chalarangelo/pen/wvbwQKg

## Grid centering

Using the **grid module** is very similar to flexbox and also a common technique, especially if you are **already using grid in your layout**. The only difference from the previous technique is that you use `display: grid` in the container element and then center the child element the same way as before.

```html
<div class="grid-centering">
  <div class="content">Content</div>
</div>
```

```css
.grid-centering {
  display: grid;
  justify-content: center;
  align-items: center;
}
```

https://codepen.io/chalarangelo/pen/OJYLaNb

## Transform centering

Transform centering uses, as the name implies, **CSS transforms** to center an element. It depends on the container element having a `position: relative`, allowing the child element to utilize `position: absolute` to position itself. Then `left: 50%` and `top: 50%` are used to **offset** the child element and `transform: translate(-50%, -50%)` to **negate** its position.

```html
<div class="transform-centering">
  <div class="content">Content</div>
</div>
```

```css
.transform-centering {
  position: relative;
}

.transform-centering > .content {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
```

https://codepen.io/chalarangelo/pen/OJYLapp

## Table centering

Last but not least, **table** centering is an older technique which you might favor when working with **older browsers**. It depends on the use of `display: table` in the container element, making it behave like a `<table>` element. This allows the child element to use `display: table-cell`, behaving like a `<td>`, in combination with `text-align: center` and `vertical-align: middle` to center itself horizontally and vertically. Note that the parent element must have a fixed `width` and `height`.

```html
<div class="table-centering">
  <div class="content">Content</div>
</div>
```

```css
.table-centering {
  display: table;
  height: 100%;
  width: 100%;
}

.table-centering > .content {
  display: table-cell;
  text-align: center;
  vertical-align: middle;
}
```

https://codepen.io/chalarangelo/pen/jOoNQmZ

---

### Snippets Css S Circular Progress Bar (`content/snippets/css/s/circular-progress-bar.md`)

---
title: How can I create a circular progress bar using only CSS?
shortTitle: Circular progress bar
language: css
tags: [visual,animation]
cover: clouds-over-mountains
excerpt: Circular progress bars are fairly common in the web. Luckily, some new CSS features make it easier than ever to create them.
listed: true
dateModified: 2023-12-23
---

Circular progress bars are a fairly common element in today's websites. Yet, to many developers, they seem like quite the intimidating challenge. The truth of the matter is that getting the basics right is not that hard. In fact, with the help of some new CSS features, it's easier than ever.

## Anatomy of a circular progress bar

A circular progress bar is, at the simplest, **two circles stacked on top of each other**. The bottom circle is the **background**, and the top circle is the **progress indicator**. We'll get to how we fill the progress indicator in a bit, but the basic structure can be easily built using an `<svg>` element and a little bit of CSS.

```html
<svg width="250" height="250" viewBox="0 0 250 250">
  <circle class="bg"
    cx="125" cy="125" r="115" fill="none" stroke="#ddd" stroke-width="20"
  ></circle>
  <circle class="fg"
    cx="125" cy="125" r="115" fill="none" stroke="#5394fd" stroke-width="20"
  ></circle>
</svg>
```

```css
circle.fg {
  transform: rotate(-90deg);
  transform-origin: 125px 125px;
}
```

As you can see, the only piece of CSS we need to get the basic structure right is a `transform` property. We **rotate the foreground circle by 90 degrees**, and we set the `transform-origin` to the **center of the circle**. This way, the circle is rotated around its center, and the progress indicator starts at the top.

### Doing the math

Before we move forward, we might as well take a moment to understand the math behind the code.

The two values we need to decide are the **size** of the progress bar and the **width of the stroke**. For this example, we settled on a size of `250px` and a stroke width of `20px`. We'll use these values to calculate the rest of the values we need.

The size is use to set the `width` and `height` attributes of the SVG element, as well as the `viewBox` attribute. Dividing it by two, we get `125px`, which corresponds to the coordinates of the **center of the image**. This value is used to set the `cx` and `cy` attributes of the circles.

Taking into account the stroke width, we can calculate the **radius of the circle**. The radius is the distance from the center of the circle to the edge. In this case, the radius is `115px`, which is the size of the image, minus the stroke width, divided by two.

Finally, we can calculate the **circumference of the circle**. The circumference is the length of the edge of the circle. In this case, the circumference is `722.5px`, which is `2 * π * 115px`.

| Variable | Value | Formula |
| -------- | ----- | ------- |
| **size** | `250px` | N/A (user defined) |
| **stroke** | `20px` | N/A (user defined) |
| **center** | `125px` | `size / 2` |
| **radius** | `115px` | `(size - stroke) / 2` |
| **circumference** | `722.5px` | `2 * π * radius` |

These numbers will start coming in handy as we move forward, but I promise this is almost all the math we'll need to do.

## Filling the progress indicator

Now that we have the basic structure in place, we need to **fill the progress indicator**. To do this, we'll use the `stroke-dasharray` property, which takes alternating values for lengths and dashes.

To create a progress bar, we want to pass two values: the length of the filled part, and the length of the empty part. To get the **filled part** we multiply the progress percentage by the circumference of the circle. To get the **empty part**, we subtract the filled part from the circumference.

Supposing we want to fill 50% of the circle, the SVG code would look like this:

```html
<svg width="250" height="250" viewBox="0 0 250 250">
  <circle class="bg"
    cx="125" cy="125" r="115" fill="none" stroke="#ddd" stroke-width="20"
  ></circle>
  <circle class="fg"
    cx="125" cy="125" r="115" fill="none" stroke="#5394fd" stroke-width="20"
    stroke-dasharray="361.25 361.25"
  ></circle>
```

## Making the progress bar dynamic

Hardcoding the `stroke-dasharray` value is not very useful. We want to be able to **set the progress percentage dynamically**. This is where [CSS variables](/css/s/variables) and the math from before come into play.

Given a `--progress` variable, we can calculate the `stroke-dasharray` relatively easily. Knowing we will need most of the values from before, we can set them as CSS variables, too. Even better, most of the SVG attributes we want to set can be manipulated using CSS.

Here's what the restructured code looks like:

```html
<svg
  width="250" height="250" viewBox="0 0 250 250"
  class="circular-progress" style="--progress: 50"
>
  <circle class="bg"></circle>
  <circle class="fg"></circle>
</svg>
```

```css
.circular-progress {
  --size: 250px;
  --half-size: calc(var(--size) / 2);
  --stroke-width: 20px;
  --radius: calc((var(--size) - var(--stroke-width)) / 2);
  --circumference: calc(var(--radius) * pi * 2);
  --dash: calc((var(--progress) * var(--circumference)) / 100);
}

.circular-progress circle {
  cx: var(--half-size);
  cy: var(--half-size);
  r: var(--radius);
  stroke-width: var(--stroke-width);
  fill: none;
  stroke-linecap: round;
}

.circular-progress circle.bg {
  stroke: #ddd;
}

.circular-progress circle.fg {
  transform: rotate(-90deg);
  transform-origin: var(--half-size) var(--half-size);
  stroke-dasharray: var(--dash) calc(var(--circumference) - var(--dash));
  transition: stroke-dasharray 0.3s linear 0s;
  stroke: #5394fd;
}
```

This may look like a lot, but it's mostly just setting CSS variables and using them to calculate the values we need. One cool thing I want to point out is that the `pi` constant is available as part of the `calc()` function! I didn't know this until I started writing this article, and I'm very excited about it.

At this point, if you use some JavaScript to manipulate the value of the `--progress` variable, you'll see the progress bar fill up. The added `transition` property will make the progress bar animate smoothly.

## Timed progress bar

Have you ever watched an ad inside a mobile game? You know, the ones that give you a reward if you watch the whole thing? They usually have a progress bar that fills up as the ad plays. Or it empties as you watch, much like a countdown timer. Whatever flavor you might have seen, they are variations of the same concept.

_How can we create a progress bar that fills up over a predetermined amount of time?_ We could go about it using JavaScript and `Window.requestAnimationFrame()`, but that wouldn't be very cool. Instead, we can use the `animation` property to animate the `--progress` variable from `0` to `100` over a set amount of time.

```css
@keyframes progress-animation {
  from {
    --progress: 0;
  }
  to {
    --progress: 100;
  }
}
```

If you try hooking this up to our SVG, you'll notice it doesn't work exactly like you'd think. This is due to the fact that the browser doesn't really know what to do with the `--progress` variable. It doesn't know it's a number, so it doesn't know how to animate it.

Luckily, CSS has come up with a solution for this. The `@property` rule allows us to define custom properties and tell the browser what type they are. In this case, we want to tell the browser that `--progress` is a number.

```css
@property --progress {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}
```

<baseline-support featureId="registered-custom-properties">
</baseline-support>

Now that the browser knows what to do with the `--progress` variable, we can hook it up to the animation.

```css
.circular-progress {
  animation: progress-animation 5s linear 0s 1 forwards;
}
```

This will animate the `--progress` variable from `0` to `100` over 5 seconds. The `forwards` keyword tells the browser to keep the final value of the animation. Without it, the progress bar would reset to `0` after the animation is done. You can create the opposite effect by setting the `animation-direction` property to `reverse` and using `backwards` instead of `forwards`.

## Putting it all together

We've covered a lot of ground in this article. We've gone from a simple SVG element to a fully functional progress bar. We've used CSS variables, math functions, and even a new CSS feature. Let's take a look at the final code.

```html
<svg width="250" height="250" viewBox="0 0 250 250" class="circular-progress">
  <circle class="bg"></circle>
  <circle class="fg"></circle>
</svg>
```

```css
.circular-progress {
  --size: 250px;
  --half-size: calc(var(--size) / 2);
  --stroke-width: 20px;
  --radius: calc((var(--size) - var(--stroke-width)) / 2);
  --circumference: calc(var(--radius) * pi * 2);
  --dash: calc((var(--progress) * var(--circumference)) / 100);
  animation: progress-animation 5s linear 0s 1 forwards;
}

.circular-progress circle {
  cx: var(--half-size);
  cy: var(--half-size);
  r: var(--radius);
  stroke-width: var(--stroke-width);
  fill: none;
  stroke-linecap: round;
}

.circular-progress circle.bg {
  stroke: #ddd;
}

.circular-progress circle.fg {
  transform: rotate(-90deg);
  transform-origin: var(--half-size) var(--half-size);
  stroke-dasharray: var(--dash) calc(var(--circumference) - var(--dash));
  transition: stroke-dasharray 0.3s linear 0s;
  stroke: #5394fd;
}

@property --progress {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

@keyframes progress-animation {
  from {
    --progress: 0;
  }
  to {
    --progress: 100;
  }
}
```

And here's a CodePen of the code in action:

https://codepen.io/chalarangelo/pen/mdodgeL

## Conclusion

Using modern HTML and CSS, we created a circular progress bar. This setup serves as a good starting point for you to experiment with. You can use it as is, or you can extend it to suit your needs, sprinkling in a little bit of JavaScript if you need to. You can even convert it into a Web Component or a React component for your projects.

---

### Snippets Css S Clearfix (`content/snippets/css/s/clearfix.md`)

---
title: How to clear floats with a clearfix
shortTitle: Clearfix
language: css
tags: [layout]
cover: memories-of-pineapple-3
excerpt: Eliminate the need for non-semantic markup to clear floats.
listed: true
dateModified: 2024-08-06
---

When using the old-school `float` property to build layouts, you may encounter issues with elements not clearing their floated children. This can lead to unexpected **layout problems**, such as elements overlapping or not aligning properly.

To fix this issue, the **clearfix** hack was developed, which allows an element to self-clear its children without the need for additional non-semantic markup.

What you need to do is to create a pseudo-element with `content: ''` and apply `clear: both` to it. This will make the element clear past both left and right floats, ensuring that the layout behaves as expected.

For this technique to work properly, make sure there are **no non-floating children** in the container and that there are no tall floats before the container with the clearfix but in the same formatting context (e.g. floated columns).

> [!NOTE]
>
> This is only useful if you are using `float` to build layouts. Consider using a more modern approach, such as the flexbox or grid layout.

```html
<div class="clearfix">
  <div class="floated">float a</div>
  <div class="floated">float b</div>
  <div class="floated">float c</div>
</div>
```

```css
.clearfix::after {
  content: '';
  display: block;
  clear: both;
}

.floated {
  float: left;
  padding: 4px;
}
```

---

### Snippets Css S Code Reviews (`content/snippets/css/s/code-reviews.md`)

---
title: How do you review CSS code in Pull Requests?
shortTitle: CSS code reviews
language: css
tags: [webdev]
cover: green-css
excerpt: Reviewing CSS code is a skill that takes time to master. Here are some tips from my personal experience to help you get started.
listed: true
dateModified: 2023-05-21
---

Reviewing CSS code is a bit different than reviewing JavaScript code. In fact, many developers pay little attention to CSS, which can lead to problems down the line. Yet, many developers don't know how to review CSS code properly. In this post, we'll go over some of the things you should look for when reviewing CSS code in Pull Requests.

## Visual check

The very first step when reviewing CSS is to check if the **result looks right**. This might mean comparing the end result to a **design mockup** or checking if every interaction behaves according to the animation principles the team has agreed upon. **Responsiveness** is also a key factor to consider, so you should check if the page looks right on **different screen sizes**. The visual check is probably the most straightforward and important step, but unfortunately many developers stop there.

## Code style

Your team should have set up a **linter and formatter** for CSS and, if you haven't, you should do it as soon as possible. This will help you enforce a consistent code style and make the code easier to read and maintain. Provided that's the case, you should check for **conventions** that the linter can't automatically enforce. These often include naming conventions, proper documentation or the use of CSS custom properties in place of hard-coded values.

## Specificity

CSS selectors can be easily abused, causing headaches later down the line. Having clear conventions usually resolves a lot of issues, but things can slip through the cracks. Ensuring specificity is **as low as possible** and that selectors are **not too generic or overly complex** will help increase the code's maintainability.

## Leftovers

In an ideal scenario, the Pull Request author has a clear vision of the CSS they are writing and everything works out perfectly the first time. As you know, that's rarely the case, meaning experimentation and changes will happen during development. As the code changes, some **old code** might hang around without contributing anything to the page. One of the most common examples I've stumbled upon are flexbox-related properties for non-flexbox elements. These take a bit of time to spot, but they can be easily removed, saving you problems in the future.

## Performance

CSS performance is very often overlooked. Simple rules, deduplication and minimum overrides are some of the things that can be done to improve performance. Understandably, this sort of opportunity is **hard to spot**, but it's worth keeping an eye out for. That being said, don't go overboard with performance optimizations. If you're not sure if something is worth it, you should probably leave it as is.

---

### Snippets Css S Content Aware Height Transition (`content/snippets/css/s/content-aware-height-transition.md`)

---
title: Content-aware height transition
language: css
tags: [animation]
cover: washed-ashore
excerpt: Dynamically transition an element's height, based on its content.
listed: true
dateModified: 2024-08-24
---

As you may be aware, the `height` property cannot be transitioned from `0` to `auto` directly. Instead, we usually rely on `max-height` to transition an element's height from `0` to a known value. However, when **the height of the element is unknown**, we can use a combination of JavaScript and CSS variables to transition the height from `0` to `auto`.

The trick here is to leverage [CSS custom properties](/css/s/variables) to alter the `max-height`. Then, using JavaScript, we can change the value of the custom property to the actual **height of the element's content**.

So, how do we do that, exactly? Provided we've defined a `--max-height` CSS variable with, we can use `Element.scrollHeight` and `CSSStyleDeclaration.setProperty()` to set the value of the custom property to the **current height of the element**. This way, we can essentially transition the height of the element from `0` to `auto`.

In order to complete the effect and apply it on hover, we'll use the `:hover` pseudo-class to change the `max-height` to the value of the `--max-height` variable set by JavaScript. We'll also add a `transition` for the `max-height` property to **animate the height change**.

And voilà! You've got yourself a content-aware height transition.

https://codepen.io/chalarangelo/pen/OJeKKBZ

> [!CAUTION]
>
> This trick causes a **reflow on each animation frame**, which will be laggy if there are a large number of elements beneath the element that is transitioning in height.

```html
<div class="trigger">
  Hover me to see a height transition.
  <div class="el">Additional content</div>
</div>
```

```css
.el {
  transition: max-height 0.3s;
  overflow: hidden;
  max-height: 0;
}

.trigger:hover > .el {
  max-height: var(--max-height);
}
```

```js
const el = document.querySelector('.el');
const height = el.scrollHeight;

el.style.setProperty('--max-height', height + 'px');
```

---

### Snippets Css S Current Color (`content/snippets/css/s/current-color.md`)

---
title: The currentColor CSS keyword
language: css
tags: [visual]
cover: picking-berries
excerpt: The `currentColor` CSS keyword is a nifty alternative to custom properties for simple use cases.
listed: true
dateModified: 2022-11-30
---

Modern CSS supports custom properties, yet the `currentColor` keyword precedes them by a few years. Thus, you might still find it in the wild and it is worth knowing what it does and how it works.

```html
<p>My <span>background</span> is the same color as my <a href="#">text</a>.</p>
```

```css
p {
  color: #101010;
}

p, p > * {
  background-color: currentColor;
}

a {
  color: #0077ff;
}

span {
  color: #fd203a;
}
```

`currentColor` contains the current value of the `color` property of the element. It is useful when you want to use the same color for multiple properties, such as `border-color` or `background-color`. It also respects the cascade, so if no value is provided for `color`, it will use the value of the `color` property of the parent element.

---

### Snippets Css S Custom Checkbox Radio (`content/snippets/css/s/custom-checkbox-radio.md`)

---
title: Styling checkboxes and radio buttons
language: css
tags: [visual,animation]
cover: interior-8
excerpt: Learn how to create customized and animated checkboxes and radio buttons with CSS.
listed: true
dateModified: 2024-09-14
---

One of the most common pain points in web development is styling checkboxes and radio buttons. They are notoriously difficult to style consistently across browsers and platforms. However, with a little CSS magic, you can create custom checkboxes and radio buttons that look great and are easy to use.

https://codepen.io/chalarangelo/pen/QWeNvea

## Custom checkboxes

For a custom checkbox, the best solution I've found is to use an `<svg>` element to create the checkmark symbol and insert it via the `<use>` element to create a **reusable SVG icon**.

You can then create a container and use flexbox to create the appropriate layout for the checkboxes. Hide the `<input>` element and use the `<label>` associated with it to display a checkbox and the provided text.

Then, using `stroke-dashoffset`, you can animate the check symbol on state change. Finally, use `transform: scale(0.9)` via a CSS animation to create a zoom animation effect.

```html
<svg class="checkbox-symbol">
  <symbol id="check" viewbox="0 0 12 10">
    <polyline
      points="1.5 6 4.5 9 10.5 1"
      stroke-linecap="round"
      stroke-linejoin="round"
      stroke-width="2"
    ></polyline>
  </symbol>
</svg>

<div class="checkbox-container">
  <input class="checkbox-input" id="apples" type="checkbox" />
  <label class="checkbox" for="apples">
    <span>
      <svg width="12px" height="10px">
        <use xlink:href="#check"></use>
      </svg>
    </span>
    <span>Apples</span>
  </label>
  <input class="checkbox-input" id="oranges" type="checkbox" />
  <label class="checkbox" for="oranges">
    <span>
      <svg width="12px" height="10px">
        <use xlink:href="#check"></use>
      </svg>
    </span>
    <span>Oranges</span>
  </label>
</div>
```

```
/* Detailed source-code truncated for AI context efficiency. */
```

## Custom radio buttons

For a custom radio button, you can use **pseudo-elements** to do the heavy lifting. Same as before, create a container and use flexbox to create the appropriate layout for the radio buttons. Reset the styles on the `<input>` element and use it to create the outline and background of the radio button.

Then, use the `::before` pseudo-element to create the inner circle of the radio button. Use `transform: scale(1)` and a CSS transition to create an animation effect on state change.

```html
<div class="radio-container">
  <input class="radio-input" id="apples" type="radio" name="fruit" />
  <label class="radio" for="apples">Apples</label>
  <input class="radio-input" id="oranges" type="radio" name="fruit" />
  <label class="radio" for="oranges">Oranges</label>
</div>
```

```css
.radio-container {
  box-sizing: border-box;
  background: #ffffff;
  color: #222;
  height: 64px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-flow: row wrap;
}

.radio-container * {
  box-sizing: border-box;
}

.radio-input {
  appearance: none;
  background-color: #ffffff;
  width: 16px;
  height: 16px;
  border: 1px solid #cccfdb;
  margin: 0;
  border-radius: 50%;
  display: grid;
  align-items: center;
  justify-content: center;
  transition: all 0.3s ease;
}

.radio-input::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  transform: scale(0);
  transition: 0.3s transform ease-in-out;
  box-shadow: inset 6px 6px #ffffff;
}

.radio-input:checked {
  background: #0077ff;
  border-color: #0077ff;
}

.radio-input:checked::before {
  transform: scale(1);
}

.radio {
  cursor: pointer;
  padding: 6px 8px;
}

.radio:not(:last-child) {
  margin-right: 6px;
}
```

---

### Snippets Css S Custom Scrollbar (`content/snippets/css/s/custom-scrollbar.md`)

---
title: Create a custom scrollbar with CSS
language: css
tags: [visual]
cover: sea-view
excerpt: Customize the scrollbar style for elements with scrollable overflow, using just CSS.
listed: true
dateModified: 2024-09-17
---

Scrollbar styling is one of the longest-awaited features in CSS. While it's not officially supported, there are still ways to style scrollbars in certain browsers.

> [!CAUTION]
>
> Scrollbar styling doesn't appear to be on any standards track. This technique only works on **WebKit-based browsers** (Chrome, Edge, Safari).

In general, you can use the `::-webkit-scrollbar` pseudo-element to style the **scrollbar element**, `::-webkit-scrollbar-track` to style the **scrollbar track** (the background of the scrollbar), and `::-webkit-scrollbar-thumb` to style the **scrollbar thumb** (the draggable element).

Putting everything together, you can create a style for the scrollbar like this:

```css
.custom-scrollbar::-webkit-scrollbar {
  width: 8px;
}

.custom-scrollbar::-webkit-scrollbar-track {
  background: #4a7856;
  border-radius: 12px;
}

.custom-scrollbar::-webkit-scrollbar-thumb {
  background: #70bceb;
  border-radius: 12px;
}
```

https://codepen.io/chalarangelo/pen/JjgXePq

---

### Snippets Css S Details Tabbed Content (`content/snippets/css/s/details-tabbed-content.md`)

---
title: Creating tabbed content without JavaScript
shortTitle: Details tabbed content
language: css
tags: [interactivity]
cover: flower-portrait-6
excerpt: Leverage modern HTML and CSS capabilities to create a tabbed content component without JavaScript.
listed: true
dateModified: 2025-05-26
---

A few weeks back, I was working on a **tabbed content component** and wondered if I could leverage the `<details>` HTML element to create a more semantic and accessible solution. After some experimentation, I found that it was indeed possible, but there's a small caveat: it needs the `::details-content` pseudo-element to work properly.

<baseline-support featureId="details-content">
</baseline-support>

As you may be aware by now, the `<details>` element creates collapsable sections of content. Adding the same `name` attribute to multiple `<details>` elements allows you to create an accordion-style component. We can build on top of that to create a tabbed content component.

```html
<div class="tabs-container">
  <details name="tabs" open>
    <summary>First tab</summary>
    <div><p>First tab content</p></div>
  </details>
  <details name="tabs">
    <summary>Second tab</summary>
    <div><p>Second tab content</p></div>
  </details>
</div>
```

@[Quick refresher](/html/s/details-accordion)

In order for this trick to work, we'll have to wrap all of our elements in a `<div>` or similar **container** and make it a **grid**. We'll then set up our grid to be `N x 2`, where `N` is the **number of tabs** we want.

```css
.tabs-container {
  position: relative;
  display: grid;
  grid-template-rows: auto 1fr;
  /* For 2 tabs, adjust to your needs */
  grid-template-columns: repeat(2, auto);
}
```

The **first row** will contain our tab _handles_ (the `<summary>` elements), and the **second row** will contain the content of each tab. We'll use the appropriate `grid-row` and `grid-column` properties to set these up correctly.

```css
.tabs-container > details > summary {
  grid-row: 1;
}

.tabs-container > details > div {
  grid-column: 1 / -1;
}
```

In order for the open tab to display correctly, we'll need to set the `<details>` element, along with the `::details-content` pseudo-element to `display: contents`. This essentially means that **the element will not generate a box**, but its children will be displayed as if they were direct children of the parent element. This allows us to keep the semantic structure of the HTML while still achieving the desired layout.

```css
.tabs-container > details {
  display: contents;
}

.tabs-container > details[open]::details-content {
  display: contents;
}
```

And that's pretty much all that's needed. Slap a few styles to make it pretty and you've got yourself a **JavaScript-free** tabbed content component.

https://codepen.io/chalarangelo/pen/JoodqXo

```html
<div class="tabs-container">
  <details name="tabs" open>
    <summary>First tab</summary>
    <div><p>First tab content</p></div>
  </details>
  <details name="tabs">
    <summary>Second tab</summary>
    <div><p>Second tab content</p></div>
  </details>
</div>
```

```css
.tabs-container {
  position: relative;
  display: grid;
  grid-template-rows: auto 1fr;
  grid-template-columns: repeat(2, auto);
}

.tabs-container > details {
  display: contents;
}

.tabs-container > details > summary {
  display: flex;
  justify-content: center;
  grid-row: 1;
}

/* Hides the ::marker pseudo-element */
.tabs-container > details > summary::marker {
  display: none;
  content: "";
}

.tabs-container > details > div {
  grid-column: 1 / -1;
}

.tabs-container > details[open]::details-content {
  display: contents;
}
```

---

### Snippets Css S Drop Caps (`content/snippets/css/s/drop-caps.md`)

---
title: Drop caps with pure CSS
shortTitle: Drop caps
language: css
tags: [visual]
cover: bamboo-lamp
excerpt: Style the first letter of the first paragraph in your text, using a simple CSS selector,
listed: true
dateModified: 2024-08-23
---

The `::first-letter` CSS pseudo-element is used to style the **first letter of the first line** of a block-level element. This is often used to create a drop cap effect, where the first letter is larger than the rest of the text.

Most drop cap effects are only applied to the **first paragraph**, so you'll need to combine the previous selector with the `:first-child` pseudo-class to achieve this effect.

https://codepen.io/chalarangelo/pen/oNrKNWK

Then, you can style the first letter of the first paragraph using the `::first-letter` pseudo-element. More often than not, you'll want to alter its `font-size`, `font-weight`, `line-height`, and `color`. Additionally, to make it look more like a drop cap, you can `float` it to the left and add some `margin`.

```css
p:first-child::first-letter {
  color: #5f79ff;
  float: left;
  margin: 0 8px 0 4px;
  font-size: 3rem;
  font-weight: bold;
  line-height: 1;
}
```

---

### Snippets Css S Dynamic Shadow (`content/snippets/css/s/dynamic-shadow.md`)

---
title: Dynamic shadow
language: css
tags: [visual]
cover: couch-laptop
excerpt: Learn how to create a shadow similar to `box-shadow`, but based on the colors of the element itself.
listed: true
dateModified: 2024-09-04
---

Ever wanted to create a shadow that matches the colors of the element itself? Unfortunately, `box-shadow` is quite limited in that regard. However, you can create a similar effect using a pseudo-element and some clever CSS.

Given an element with `position: relative`, you can set `position: absolute` on its `::after` pseudo-element to **fill the available space**. By setting `background: inherit`, the pseudo-element will inherit the `background` of the parent element.

In order to create the shadow, you can then slightly **offset the pseudo-element** using `top` and apply a `blur()` filter to create a shadow, and make it semi-transparent using `opacity`.

Finally, you can position the pseudo-element behind its parent by setting `z-index: -1` on the pseudo-element. Due to the way the **stacking context** works, the parent itself should not define a value for `z-index` but rather its parent needs to have a `z-index` value for the element itself to display above the `::after` pseudo-element.


```css
.container {
  z-index: 1;
}

.dynamic-shadow {
  position: relative;
  background: linear-gradient(75deg, #6d78ff, #00ffb8);
}

.dynamic-shadow::after {
  content: '';
  width: 100%;
  height: 100%;
  position: absolute;
  background: inherit;
  top: 0.5rem;
  filter: blur(0.4rem);
  opacity: 0.7;
  z-index: -1;
}
```

This effect can work well with images, gradients or any other sort of background that you want to create a shadow for. You can also adjust the `blur()` value to create a more or less pronounced shadow effect.

https://codepen.io/chalarangelo/pen/XWvmNEQ

---

### Snippets Css S Easing Variables (`content/snippets/css/s/easing-variables.md`)

---
title: CSS easing variables
language: css
tags: [animation]
cover: curve
excerpt: Learn how to use the `cubic-bezier()` class of easing functions and create beautiful animations that stand out.
listed: true
dateModified: 2021-06-12
---

Most web developers use the built-in `ease`, `ease-in`, `ease-out` or `ease-in-out` functions for most use-cases of `transition-timing-function` in their designs. While these are perfectly fine for everyday use, there's a far more powerful, yet intimidating option available, the [`bezier-curve()`](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) function.

Using the `bezier-curve()` we can easily define custom easing variables that can help our designs pop out. In fact the built-in functions mentioned above can also be written using the `bezier-curve()` function. Here's a handful of useful easing functions stored in CSS variables for ease of use:

```css
:root {
  /* ease-in corresponds to cubic-bezier(0.42, 0, 1.0, 1.0) */
  --ease-in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
  --ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19);
  --ease-in-quart: cubic-bezier(0.895, 0.03, 0.685, 0.22);
  --ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
  --ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035);
  --ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.335);

  /* ease-out corresponds to cubic-bezier(0, 0, 0.58, 1.0) */
  --ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
  --ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
  --ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
  --ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);

  /* ease-in-out corresponds to cubic-bezier(0.42, 0, 0.58, 1.0) */
  --ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
  --ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
  --ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
  --ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
  --ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
}
```

---

### Snippets Css S Engraved Embossed Text (`content/snippets/css/s/engraved-embossed-text.md`)

---
title: Engraved and embossed text
language: css
tags: [visual]
cover: memories-of-pineapple-2
excerpt: Make the text appear engraved or embossed into the background, using CSS.
listed: true
dateModified: 2024-09-16
---

Engraved and embossed text effects are pretty similar to each other. The difference is that the engraved text appears to be carved into the background, while the embossed text appears to be raised from the background.

For both effects, you'll need to use `background-color` combined with `background-clip` to **clip the background to the text**. Then, you can use `color` to set the text color and `text-shadow` to add a shadow to the text.

For the **engraved text**, the `text-shadow` should be lighter than the background, while for the **embossed text**, it should be darker. Additionally, the placement of the shadow should be slightly different for each effect.

```css
.engraved-text {
  background-color: #666666;
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  text-shadow: 3px 5px 1px rgba(245, 245, 245, 0.5);
}

.embossed-text {
  background-color: #666666;
  background-clip: text;
  -webkit-background-clip: text;
  color: #f0f0f0;
  text-shadow: 1px 4px 4px #555555;
}
```

https://codepen.io/chalarangelo/pen/dyxMzZd

---

### Snippets Css S Evenly Distributed Children (`content/snippets/css/s/evenly-distributed-children.md`)

---
title: Evenly distributed children
language: css
tags: [layout]
cover: little-bird
excerpt: Evenly distribute child elements within a parent element, using Flexbox.
listed: true
dateModified: 2024-09-06
---

Have you ever struggled with distributing child elements evenly within a parent element? Flexbox makes it easy to achieve this effect, using the `justify-content` property.

All you really need is a container with `display: flex`. Then, you can use `justify-content: space-between` to **evenly distribute the child elements** on the main axis.

If your container is **horizontal** (`flex-direction: row`), the first item will be positioned at the left edge, while the last item will be positioned at the right edge. If your container orientation is **vertical** (`flex-direction: column`), the first item will be positioned at the top edge, while the last item will be positioned at the bottom edge.

Alternatively, you can use `justify-content: space-around` to **distribute the children with space around them**, instead of between them. In this case the space between each pair of adjacent items will be half the space before the first item and after the last item.

```css
.evenly-distributed-children {
  display: flex;
  justify-content: space-between;
}

.evenly-spaced-children {
  display: flex;
  justify-content: space-around;
}
```

> [!NOTE]
>
> You may want to check the [Flexbox cheatsheet](/css/s/flexbox-cheatsheet) for more information on Flexbox properties and values.

---

### Snippets Css S Fit Image In Container (`content/snippets/css/s/fit-image-in-container.md`)

---
title: How can I fit an image in a container with CSS?
shortTitle: Fit image in container
language: css
tags: [layout,visual]
cover: succulent-3
excerpt: Fit and position an image appropriately inside its container while preserving its aspect ratio with this handy technique.
listed: true
dateModified: 2024-08-26
---

Have you ever wanted to **fit an image** inside a container while **preserving its aspect ratio**? This is a common requirement when working with images, and it can be achieved using the `object-fit` and `object-position` properties in CSS.

Generally, you can use `object-fit: contain` to fit the **entire image** within the container while preserving its aspect ratio. On the other hand, `object-fit: cover` will **fill the container** with the image while preserving its aspect ratio.

You can also use `object-position` to position the image within the container. For example, `object-position: center` will center the image, while `object-position: right top` will position it at the top right corner.

```css
.image-contain {
  object-fit: contain;
  object-position: center;
}

.image-cover {
  object-fit: cover;
  object-position: right top;
}
```

https://codepen.io/chalarangelo/pen/KKOwRRZ

---

### Snippets Css S Flexbox Cheatsheet (`content/snippets/css/s/flexbox-cheatsheet.md`)

---
title: Flexbox Cheat Sheet
language: css
tags: [layout,flexbox,cheatsheet]
cover: frames
excerpt: Flexbox allows you to create fluid layouts easily. If you are constantly looking up how it works, this handy cheatsheet is all you need.
listed: true
dateModified: 2021-06-12
---

## Container

- `display: flex` or `display: inline-flex`: creates a flex context (or an inline flex context) for direct children of this element
- `flex-direction` determines the main and cross axis for the container, valid values are:
  - `row` (default): horizontal, in the direction of writing (left to right for English)
  - `row-reverse`: horizontal, in the opposite direction of writing (right to left for English)
  - `column`: vertical, top to bottom
  - `column-reverse`: vertical, bottom to top
- `flex-wrap` determines if flex items will try to fit in one line, valid values are:
  - `nowrap` (default): all flex items will be on one line
  - `wrap`: flex items will wrap onto multiple lines, top to bottom
  - `wrap-reverse`: flex items will wrap onto multiple lines, bottom to top
- `flex-flow`: shorthand combining `flex-direction` and `flex-wrap`
  - Formal syntax: `flex-flow: <'flex-direction'> || <'flex-wrap'>`
- `justify-content` defines the alignment along the main axis, valid values are:
  - `flex-start` (default): pack flex items from the start
  - `flex-end`: pack flex items from the end
  - `start`: pack items from the start
  - `end`: pack items from the end
  - `left`: pack items from the left
  - `right`: pack items from the right
  - `center`: pack items around the center
  - `space-around`: distribute items evenly with equal space around them
  - `space-between`: distribute items evenly with equal space between them
  - `space-evenly`: distribute items evenly, ensuring equal space between any two items
  - `stretch`: distribute items evenly, stretching auto-sized items to fit the container
- `align-items` defines the alignment along the cross axis, valid values are:
  - `flex-start` (default): pack flex items from the start
  - `flex-end`: pack flex items from the end
  - `start`: pack items from the start
  - `end`: pack items from the end
  - `center`: pack items around the center
  - `baseline`: align items based on their baselines
  - `stretch`: stretch items to fill the container
- `align-content` defines the alignment of extra space along the cross axis, valid values are:
  - `flex-start` (default): pack flex items from the start
  - `flex-end`: pack flex items from the end
  - `start`: pack items from the start
  - `end`: pack items from the end
  - `center`: pack items around the center
  - `space-around`: distribute items evenly with equal space around them
  - `space-between`: distribute items evenly with equal space between them
  - `space-evenly`: distribute items evenly, ensuring equal space between any two items
  - `stretch`: distribute items evenly, stretching auto-sized items to fit the container



## Items

- `flex-grow` determines how much the item can grow if necessary
  - Accepts a single positive number (unitless), default value is `0`
  - Specifies how much of the remaining space in the flex container should be assigned to the item
  - The remaining space is the size of the flex container minus the size of all flex items' sizes together
  - If all items have the same `flex-grow`, all items will receive an equal share of the remaining space
  - If not all items have the same `flex-grow`, the remaining space is distributed according to the ratio defined by these values
- `flex-shrink` determines how much the items can shrink if necessary
  - Accepts a single positive number (unitless), default value is `1`
  - If the size of all flex items is larger than the flex container, items shrink to fit according to `flex-shrink`
- `flex-basis` determines the initial size of a flex item before the remaining space is distributed
  - Can use any valid `width` value, intrinsic size values, `auto` (default) or `content`
  - `auto` means "look at my `width` or `height` property", whereas `content` is used for automatic sizing
- `flex`: shorthand combining `flex-grow`, `flex-shrink` and `flex-basis`
  - Formal syntax: `flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]`
- `align-self` allows the item to override the default `align-items` specified by the container
  - Valid values are the same as those of the `align-items` property in the container
- `order` determines the ordering of the item
  - Accepts an integer value
  - Items in a container are sorted by ascending `order` value and then by their source code order
  - Might cause accessibility issues if used incorrectly

---

### Snippets Css S Floating Or Sticky List Titles (`content/snippets/css/s/floating-or-sticky-list-titles.md`)

---
title: List with floating or sticky section headings
language: css
tags: [visual]
cover: pop-of-green
excerpt: Learn how to create a list with floating or sticky headings for each section.
listed: true
dateModified: 2024-08-29
---

Ever wanted to create a list with floating or sticky headings for each section? You absolutely can, using some clever CSS tricks.

https://codepen.io/chalarangelo/pen/ExqjMaP

> [!NOTE]
>
> This article uses the semantic HTML `<dl>`, `<dt>`, and `<dd>` elements. You can easily replace them with your own HTML structure, as needed.

## Floating headings

In order to create a list with **floating headings for each section**, you can use `overflow-y: auto` to allow the list container to overflow vertically. Then, use `display: grid` on the inner container (`<dl>`) to create a layout with two columns.

Set headings (`<dt>`) to `grid-column: 1` and content (`<dd>`) to `grid-column: 2`. Finally, apply `position: sticky` and `top: 0.5rem` to headings to create a floating effect.

```css
.floating-stack {
  overflow-y: auto;
}

.floating-stack > dl {
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  align-items: center;
}

.floating-stack dt {
  position: sticky;
  top: 0.5rem;
  grid-column: 1;
}

.floating-stack dd {
  grid-column: 2;
}
```

## Sticky headings

Similarly, for the **sticky headings**, you'll need to use `overflow-y: auto` to allow the list container (`<dl>`) to overflow vertically. Then, simply set headings (`<dt>`) to `position: sticky` and `top: 0` to create a sticky effect. You can also style the headings to make them stand out.

```css
.sticky-stack {
  overflow-y: auto;
}

.sticky-stack dt {
  position: sticky;
  top: 0;
}
```

---

### Snippets Css S Fluid Typography (`content/snippets/css/s/fluid-typography.md`)

---
title: Fluid typography with CSS clamp
shortTitle: Fluid typography
language: css
tags: [visual]
cover: shell-focus
excerpt: Learn how to create text that scales according to the viewport width.
listed: true
dateModified: 2024-08-27
---

Fluid typography is a technique that allows text to **scale according to the viewport width**. This is particularly useful for responsive designs, where you want the text to grow or shrink based on the available space.

> [!NOTE]
>
> While fluid typography is a cool technique, **hand-crafted typographic scales** can often look more polished. You may want to read more in the [typographic scale basics article](/css/s/typographic-scale-basics).

In order to create fluid typography, you can use the `clamp()` CSS function to **clamp the value** of `font-size` between a minimum and maximum value. This ensures that the text size will never be smaller than the minimum value or larger than the maximum value.

It is advised to use `rem` units for the minimum and maximum values, as they are **relative to the root font size**. This allows you to set a base font size on the `html` element and have the text scale accordingly.

Finally, you should use a **formula to calculate the responsive value** for `font-size`. This formula should be based on the viewport width, so that the text size grows or shrinks as the viewport changes, thus you can use the `vw` unit.

In the example below, the `font-size` is clamped between `1rem` and `3rem`, and the formula `8vw - 2rem` is used to calculate the responsive value. This formula ensures that the text size will be `1rem` at `600px` viewport width and `3rem` at `1000px` viewport width.

```css
.fluid-type {
  font-size: clamp(1rem, 8vw - 2rem, 3rem);
}
```

---

### Snippets Css S Footer At The Bottom (`content/snippets/css/s/footer-at-the-bottom.md`)

---
title: How can I ensure the footer is always at the bottom of the page?
shortTitle: Footer at the bottom
language: css
tags: [layout]
cover: flower-shape-sunset
excerpt: Make sure the footer stays at the bottom of the page, instead of floating up when the content is too short.
listed: true
dateModified: 2022-10-30
---

Preventing the footer from floating up the page is important when trying to create a polished website. Pages with **short content** can run into this issue, but it's easy to fix with a few lines of CSS. Assuming your HTML looks something like the snippet below, here are two modern ways to ensure the footer is always at the bottom of the page:

```html
<body>
  <main></main>
  <footer></footer>
</body>
```

## Using Flexbox

You can use flexbox to ensure that the footer is always at the bottom of the page. This is done by setting the giving the `body` element `min-height: 100dvh`, `display: flex` and `flex-direction: column`. If you need to support older browsers, you can also add a fallback for `min-height` using `100vh`.

<baseline-support featureId="viewport-unit-variants">
</baseline-support>

Then, give the `footer` element a `margin-top: auto` to make its margin fill the remaining space between it and its previous sibling. Note that this technique will not stretch the previous sibling, but rather **push the footer to the bottom of the page**.


```css
body {
  /* Fallback for browsers that don't support dvh */
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

footer {
  margin-top: auto;
}
```

## Using Grid

You can also use grid in a very similar fashion. Simply swap `display: flex` for `display: grid` and `flex-direction: column` for `grid-template-rows: 1fr auto` in the `body` element.

No additional attributes are needed for the `footer` element. In this case, the `fr` unit is leveraged to stretch the `main` element to **fill the remaining space**.

```css
body {
  /* Fallback for browsers that don't support dvh */
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  grid-template-rows: 1fr auto;
}
```

## Notes

As you can see, both techniques are straightforward to implement. Depending on your needs one might be more suitable than the other. Generally speaking, grid is more flexible in most cases and can help if you have more complex layouts, which can include a header or sidebar.

---

### Snippets Css S Fullscreen (`content/snippets/css/s/fullscreen.md`)

---
title: Style elements when in fullscreen mode
shortTitle: Fullscreen element styling
language: css
tags: [visual]
cover: flower-portrait-3
excerpt: Did you know you can differentiate an element's styles when it's in fullscreen mode? Learn how to do it with this code snippet!
listed: true
dateModified: 2024-09-08
---

Did you know you can differentiate an element's styles when it's in fullscreen mode? It doesn't even require special classes or JavaScript, as you can use the `:fullscreen` *pseudo-class* to style an element when it's displayed in fullscreen mode.

This pseudo-class is particularly useful in adjusting an element's appearance when it's in fullscreen mode. Perhaps, you want to adjust the text size, background color, or even the layout of the element to better fit the screen. This can be especially useful for media players, slideshows, or any other content that benefits from a fullscreen view.

https://codepen.io/chalarangelo/pen/bGXpEoP

```css
.element {
  /* Default styles */
}

.element:fullscreen {
  /* Styles when in fullscreen mode */
}
```

> [!NOTE]
>
> You can learn more about **requesting fullscreen access** in the [related JavaScript article](/js/s/fullscreen). The gist of it is that you can use `Element.requestFullscreen()` to make an element fullscreen and `Document.exitFullscreen()` to exit fullscreen mode.

---

### Snippets Css S Gradient Text (`content/snippets/css/s/gradient-text.md`)

---
title: Gradient text
language: css
tags: [visual]
cover: red-berries
excerpt: Modern CSS can help you create gradient text with a few lines of code. Learn how today!
listed: true
dateModified: 2024-09-10
---

I remember a time when gradients were tricky to use for backgrounds, let alone for styling text. But, with modern CSS, it's pretty easy to create gradient text with only a few lines of code, at least on WebKit browsers (Chrome, Edge, Safari).

Styling text with gradients starts the same as creating a **gradient for the background**. You use the `linear-gradient()` function to define the gradient colors and direction.

Then, you have to **apply it to the text** using the `-webkit-text-fill-color` property with a value of `transparent`. Finally, **clip the background with the text** using the `background-clip` property with a value of `text` and you're done!

```css
.gradient-text {
  background: linear-gradient(to right, #009FFF, #ec2F4B);
  -webkit-text-fill-color: transparent;
  background-clip: text;
  -webkit-background-clip: text;
}
```

https://codepen.io/chalarangelo/pen/gOVrreP

---

### Snippets Css S Hide Scrollbars (`content/snippets/css/s/hide-scrollbars.md`)

---
title: Hide scroll bars on an element
shortTitle: Hide scrollbars
language: css
tags: [visual]
cover: by-the-lighthouse
excerpt: Learn how to hide scrollbars on an element, while still allowing it to be scrollable.
listed: true
dateModified: 2024-09-07
---

Scrollbars are a common pain point when styling pages, mainly due to a lack of customization options and inconsistencies between browsers. You can **hide scrollbars** on an element while still allowing it to be **scrollable** by using the `overflow` property and some vendor-specific properties.

First off, you need to ensure your content remains scrollable, by setting `overflow: auto` on the element. Then, you can hide the scrollbars using `scrollbar-width: none` on Firefox and `display: none` on the `::-webkit-scrollbar` pseudo-element on WebKit browsers (Chrome, Edge, Safari).

```css
.no-scrollbars {
  overflow: auto;
  scrollbar-width: none;
}

.no-scrollbars::-webkit-scrollbar {
  display: none;
}
```

---

### Snippets Css S Horizontal Vertical Gallery (`content/snippets/css/s/horizontal-vertical-gallery.md`)

---
title: Image gallery with horizontal or vertical scroll
shortTitle: Image gallery
language: css
tags: [visual,interactivity]
cover: flower-portrait-5
excerpt: Create a horizontally or vertically scrollable image gallery.
listed: true
dateModified: 2024-09-05
---

<style>
/* Style CodePen embeds to fit the demos */
.codepen-wrapper > div { height: 820px; }
</style>

Image galleries are useful in various contexts, from showcasing products to displaying a collection of images. Depending on your needs, you may want to create a horizontally or vertically scrollable gallery. While CSS has come a long way, unfortunately, you'll have to get your hands dirty with JavaScript to achieve this effect.

> [!NOTE]
>
> You may first want to get caught up with [scroll snapping](/css/s/scroll-snap) to understand how it works, if you haven't already.

## Image gallery with vertical scroll

To create a **vertically scrollable image gallery**, you will need to create a container with `display: flex` and `justify-content: center` and a set of slides with `display: flex` and `flex-direction: column`.

You will also need to use `scroll-snap-type: y mandatory` and `overscroll-behavior-y: contain` to create a snap effect on vertical scroll. Snap elements to the start of the container using `scroll-snap-align: start`. In order to hide the scrollbars, you can use `scrollbar-width: none` and style the pseudo-element `::-webkit-scrollbar` to `display: none`.

Then, you can use `Element.scrollTo()` to define a `scrollToElement` function that scrolls the gallery to the given item. You can use `Array.prototype.map()` and `Array.prototype.join()` to populate the `.thumbnails` element. Give each thumbnail a `data-id` attribute with the index of the image.

Using the `Document.querySelectorAll()` method, you can get all the thumbnail elements. Use `Array.prototype.forEach()` to register a handler for the `'click'` event on each thumbnail, using `EventTarget.addEventListener()` and the `scrollToElement` function.

Finally, use `Document.querySelector()` and `EventTarget.addEventListener()` to register a handler for the `'scroll'` event. Update the `.thumbnails` and `.scrollbar` elements to match the current scroll position, using the `scrollThumb` function.

https://codepen.io/chalarangelo/pen/ExqVMzW

```html
<div class="gallery-container">
  <div class="thumbnails"></div>
  <div class="scrollbar">
    <div class="thumb"></div>
  </div>
  <div class="slides">
    <div><img src="https://picsum.photos/id/1067/540/720"></div>
    <div><img src="https://picsum.photos/id/122/540/720"></div>
    <div><img src="https://picsum.photos/id/188/540/720"></div>
    <div><img src="https://picsum.photos/id/249/540/720"></div>
    <div><img src="https://picsum.photos/id/257/540/720"></div>
    <div><img src="https://picsum.photos/id/259/540/720"></div>
    <div><img src="https://picsum.photos/id/283/540/720"></div>
    <div><img src="https://picsum.photos/id/288/540/720"></div>
    <div><img src="https://picsum.photos/id/299/540/720"></div>
  </div>
</div>
```

```css
.gallery-container {
  display: flex;
  justify-content: center;
}

.thumbnails {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.thumbnails img {
  width: 40px;
  height: 40px;
  cursor: pointer;
}

.scrollbar {
  width: 1px;
  height: 720px;
  background: #ccc;
  display: block;
  margin: 0 0 0 8px;
}

.thumb {
  width: 1px;
  position: absolute;
  height: 0;
  background: #000;
}

.slides {
  margin: 0 16px;
  display: grid;
  grid-auto-flow: row;
  gap: 1rem;
  width: calc(540px + 1rem);
  padding: 0 0.25rem;
  height: 720px;
  overflow-y: auto;
  overscroll-behavior-y: contain;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
}

.slides > div {
  scroll-snap-align: start;
}

.slides img {
  width: 540px;
  object-fit: contain;
}

.slides::-webkit-scrollbar {
  display: none;
}
```

```js
const slideGallery = document.querySelector('.slides');
const slides = slideGallery.querySelectorAll('div');
const scrollbarThumb = document.querySelector('.thumb');
const slideCount = slides.length;
const slideHeight = 720;
const marginTop = 16;

const scrollThumb = () => {
  const index = Math.floor(slideGallery.scrollTop / slideHeight);
  scrollbarThumb.style.height = `${((index + 1) / slideCount) * slideHeight}px`;
};

const scrollToElement = el => {
  const index = parseInt(el.dataset.id, 10);
  slideGallery.scrollTo(0, index * slideHeight + marginTop);
};

document.querySelector('.thumbnails').innerHTML += [...slides]
  .map(
    (slide, i) => `<img src="${slide.querySelector('img').src}" data-id="${i}">`
  )
  .join('');

document.querySelectorAll('.thumbnails img').forEach(el => {
  el.addEventListener('click', () => scrollToElement(el));
});

slideGallery.addEventListener('scroll', e => scrollThumb());

scrollThumb();
```

## Image gallery with horizontal scroll

To create a **horizontally scrollable image gallery**, you will need to position its `.thumbnails` container at the bottom of the gallery, using `position: absolute`. Then, use `scroll-snap-type: x mandatory` and `overscroll-behavior-x: contain` to create a snap effect on horizontal scroll. Snap elements to the start of the container using `scroll-snap-align: start`.

Hide the scrollbars the same way as before. Use `Element.scrollTo()` to define a `scrollToElement` function that scrolls the gallery to the given item. Populate the `.thumbnails` element using `Array.prototype.map()` and `Array.prototype.join()`, giving each thumbnail a `data-id` attribute with the index of the image.

Use `Document.querySelectorAll()` to get all the thumbnail elements and register `'click'` event handlers on each thumbnail, using the `highlightThumbnail` function. Finally, register a handler for the `'scroll'` event the same as before and update the `.thumbnails` element to match the current scroll position using the `highlightThumbnail` function.

https://codepen.io/chalarangelo/pen/RwXWObY

```html
<div class="gallery-container">
  <div class="thumbnails"></div>
  <div class="slides">
    <div><img src="https://picsum.photos/id/1067/540/720"></div>
    <div><img src="https://picsum.photos/id/122/540/720"></div>
    <div><img src="https://picsum.photos/id/188/540/720"></div>
    <div><img src="https://picsum.photos/id/249/540/720"></div>
    <div><img src="https://picsum.photos/id/257/540/720"></div>
    <div><img src="https://picsum.photos/id/259/540/720"></div>
    <div><img src="https://picsum.photos/id/283/540/720"></div>
    <div><img src="https://picsum.photos/id/288/540/720"></div>
    <div><img src="https://picsum.photos/id/299/540/720"></div>
  </div>
</div>
```

```css
.gallery-container {
  position: relative;
  display: flex;
  justify-content: center;
}

.thumbnails {
  position: absolute;
  bottom: 8px;
  display: flex;
  flex-direction: row;
  gap: 6px;
}

.thumbnails div {
  width: 8px;
  height: 8px;
  cursor: pointer;
  background: #aaa;
  border-radius: 100%;
}

.thumbnails div.highlighted {
  background-color: #777;
}

.slides {
  margin: 0 16px;
  display: grid;
  grid-auto-flow: column;
  gap: 1rem;
  width: 540px;
  padding: 0 0.25rem;
  height: 720px;
  overflow-y: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.slides > div {
  scroll-snap-align: start;
}

.slides img {
  width: 540px;
  object-fit: contain;
}

.slides::-webkit-scrollbar {
  display: none;
}
```

```js
const slideGallery = document.querySelector('.slides');
const slides = slideGallery.querySelectorAll('div');
const thumbnailContainer = document.querySelector('.thumbnails');
const slideCount = slides.length;
const slideWidth = 540;

const highlightThumbnail = () => {
  thumbnailContainer
    .querySelectorAll('div.highlighted')
    .forEach(el => el.classList.remove('highlighted'));
  const index = Math.floor(slideGallery.scrollLeft / slideWidth);
  thumbnailContainer
    .querySelector(`div[data-id="${index}"]`)
    .classList.add('highlighted');
};

const scrollToElement = el => {
  const index = parseInt(el.dataset.id, 10);
  slideGallery.scrollTo(index * slideWidth, 0);
};

thumbnailContainer.innerHTML += [...slides]
  .map((slide, i) => `<div data-id="${i}"></div>`)
  .join('');

thumbnailContainer.querySelectorAll('div').forEach(el => {
  el.addEventListener('click', () => scrollToElement(el));
});

slideGallery.addEventListener('scroll', e => highlightThumbnail());

highlightThumbnail();
```

---

### Snippets Css S Hover Additional Content (`content/snippets/css/s/hover-additional-content.md`)

---
title: Reveal additional content on hover
language: css
tags: [visual]
cover: dark-leaves-5
excerpt: Learn how you can create a card that displays additional content on hover.
listed: true
dateModified: 2024-09-23
---

Fairly often, I've found myself needing to display additional content on hover. A while ago, I stumbled upon a pretty cool effect, where you can **push the image and title of a card upwards** to reveal more content. Achieving this effect is pretty simple, too.

The trick is using `overflow: hidden` on the card to hide elements that overflow vertically. Then, you can use the `:hover` and `:focus-within` pseudo-class selectors to change the card's styling if the element is hovered, focused, or any of its descendants are focused.

Then, use a `transition` to create a transition effect. Using the `margin` and `padding` properties, you can push the image and title upwards to **reveal additional content**.

https://codepen.io/chalarangelo/pen/NWQNOmj


```html
<div class="card">
  <img src="https://picsum.photos/id/404/367/267"/>
  <h3>Lorem ipsum</h3>
  <div class="focus-content">
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br/> <a href="#">Link to source</a>
    </p>
  </div>
</div>
```

```css
.card {
  width: 300px;
  height: 280px;
  padding: 0;
  box-shadow: 0 2px 4px 0 rgba(0,0,0,0.1);
  border-radius: 8px;
  box-sizing: border-box;
  overflow: hidden;
}

.card * {
  transition: 0.3s ease all;
}

.card img {
  margin: 0;
  width: 300px;
  height: 224px;
  object-fit: cover;
  display: block;
}

.card h3 {
  margin: 0;
  padding: 12px 12px 48px;
  line-height: 32px;
  font-weight: 500;
  font-size: 20px;
}

.card .focus-content {
  display: block;
  padding: 8px 12px;
}

.card p {
  margin: 0;
  line-height: 1.5;
}

.card:hover img, .card:focus-within img {
  margin-top: -80px;
}

.card:hover h3, .card:focus-within h3 {
  padding: 8px 12px 0;
}
```

---

### Snippets Css S Hover Shadow Box Animation (`content/snippets/css/s/hover-shadow-box-animation.md`)

---
title: Animate box shadow on hover
language: css
tags: [animation]
cover: dark-cloud
excerpt: Create an animated shadow box around the text when it is hovered.
listed: true
dateModified: 2024-09-18
---

One of material design's coolest tricks is the illusion of depth created by shadows. As part of hover interactions, elements are often attracted to your finger, as if they were **floating above the surface**. Luckily, it's a pretty easy effect to achieve with CSS.

The key to this effect is using `transform` with the `perspective()` function to create a **3D space** for the element, by affecting the distance between the Z plane and the user. Then, using the `translate()` function, you can reposition the element along z-axis in 3D space.

The leftover work is using `box-shadow` to create a shadow around the element and then, using `:hover`, `:active`, and `:focus` pseudo-class selectors, to apply a new `box-shadow` and `transform` to the element.

https://codepen.io/chalarangelo/pen/xxvVyoB

```css
.hover-shadow-box-animation {
  transform: perspective(1px) translateZ(0);
  box-shadow: 0 0 1px transparent;
  transition-duration: 0.3s;
  transition-property: box-shadow, transform;
}

.hover-shadow-box-animation:hover,
.hover-shadow-box-animation:focus,
.hover-shadow-box-animation:active {
  box-shadow: 1px 10px 10px -10px rgba(0, 0, 24, 0.5);
  transform: scale(1.2);
}
```

---

### Snippets Css S Hover Underline Animation (`content/snippets/css/s/hover-underline-animation.md`)

---
title: Hover underline animation
language: css
tags: [animation]
cover: coffee-phone-tray-2
excerpt: Create an animated underline effect when the user hovers over some text.
listed: true
dateModified: 2024-08-27
---

One of the fanciest text effects I've seen is an **animated underline**, where the underline appears to grow from the left side of the text when the user hovers over it. This effect can be achieved using CSS and the `::after` pseudo-element. But first, let me show you what it looks like:

https://codepen.io/chalarangelo/pen/KKOpeae

So, how does one implement this effect? Like I said, we'll make use of the `::after` pseudo-element to create the underline. It will need to have a `width` spanning the entire parent element and `position: absolute` to place it below the text content.

Naturally, the parent must then use `display: inline-block` to make the underline span just the width of the text content and `position: relative` to position the pseudo-element correctly.

Initially, we'll **hide the pseudo-element** using [`transform: scaleX(0)`](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scaleX) and **show it on hover** using [`transform: scaleX(1)`](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scaleX). To complete the effect, we'll add a `transition` to the `transform` property and adjust the `transform-origin` to make the underline appear to grow from the left side of the text.

You can adjust the color, size, and other properties to fit your design. The most notable change you can make is the `transform-origin`. Reversing the values will make the underline appear to grow from the right side of the text instead. Removing the `transform-origin` property will make the underline grow from the center of the text.

```css
.hover-underline-animation {
  display: inline-block;
  position: relative;
}

.hover-underline-animation::after {
  content: '';
  position: absolute;
  width: 100%;
  transform: scaleX(0);
  height: 2px;
  bottom: 0;
  left: 0;
  background-color: #0087ca;
  transform-origin: bottom right;
  transition: transform 0.25s ease-out;
}

.hover-underline-animation:hover::after {
  transform: scaleX(1);
  transform-origin: bottom left;
}
```

---

### Snippets Css S Image Hover Menu (`content/snippets/css/s/image-hover-menu.md`)

---
title: Show menu on image hover
language: css
tags: [layout,animation]
cover: compass
excerpt: Display a menu overlay when the user hovers over an image.
listed: true
dateModified: 2024-09-24
---

To display a menu overlay when the user hovers over an image, you can use a combination of HTML and CSS techniques. Start by using a `<figure>` element to wrap both the `<img>` element and a `<nav>` element that will contain the menu links. This structure allows you to control the layout and positioning of the image and the menu.

To create a **sliding effect**, use the `opacity` and `right` attributes to animate the image on hover. By adjusting these properties, you can make the image appear to slide to the side, revealing the menu. Additionally, set the `left` attribute of the `<nav>` to the negative of the element's width. When hovering over the parent element, reset the `left` attribute to slide the menu into view.

For the menu items, use `display: flex`, `flex-direction: column`, and `justify-content: center` on the `<nav>` to vertically center them. This ensures that the menu items are evenly spaced and aligned in the center of the menu overlay.

https://codepen.io/chalarangelo/pen/zYgqmQM

```html
<figure class="hover-menu">
	<img src="https://picsum.photos/id/1060/800/480.jpg"/>
	<nav>
		<a href="#">Home</a>
		<a href="#">Pricing</a>
		<a href="#">About</a>
	</nav>
</figure>
```

```css
.hover-menu {
  position: relative;
  overflow: hidden;
  margin: 8px;
  min-width: 340px;
  max-width: 480px;
  max-height: 290px;
  width: 100%;
  background: #000;
  text-align: center;
  box-sizing: border-box;
}

.hover-menu * {
  box-sizing: border-box;
}

.hover-menu img {
  position: relative;
  max-width: 100%;
  top: 0;
  right: 0;
  opacity: 1;
  transition: 0.3s ease-in-out;
}

.hover-menu nav {
  position: absolute;
  top: 0;
  left: -120px;
  width: 120px;
  height: 100%;
  background: #000;
  transition: 0.3s ease-in-out;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.hover-menu nav a {
  display: block;
  color: #fff;
  opacity: 0.8;
  position: relative;
  transition: 0.3s ease-in-out;
}

.hover-menu:hover img {
  opacity: 0.5;
  right: -120px;
}

.hover-menu:hover nav {
  left: 0;
  opacity: 1;
}
```

---

### Snippets Css S Image Hover Rotate (`content/snippets/css/s/image-hover-rotate.md`)

---
title: Image rotate on hover
language: css
tags: [animation,visual]
cover: succulent-1
excerpt: Zoom in and rotate your images on hover and make them stand out.
listed: true
dateModified: 2024-09-21
---

An extremely cool and modern effect is to rotate and zoom in on an image when hovering over it. All it takes is a container (e.g. a `<figure>`) with an image inside and a few lines of CSS.

To achieve the desired effect, simply use the `scale()` and `rotate()` functions in the `transform` property of the image. Then, use the `transition` property to animate the transformation when hovering over the parent element.

Add `overflow: hidden` to the parent element to hide the excess from the image transformation. You can also set a `min-width`, `max-width`, and `width` to control the size of the container.

https://codepen.io/chalarangelo/pen/BaXKqep


```html
<figure class="hover-rotate">
  <img src="/path/to/your/img.jpg"/>
</figure>
```

```css
.hover-rotate {
  overflow: hidden;
  min-width: 240px;
  max-width: 320px;
  width: 100%;
}

.hover-rotate img {
  transition: all 0.3s;
  box-sizing: border-box;
  max-width: 100%;
}

.hover-rotate:hover img {
  transform: scale(1.3) rotate(5deg);
}
```

---

### Snippets Css S Image Mosaic (`content/snippets/css/s/image-mosaic.md`)

---
title: Responsive image mosaic
language: css
tags: [layout]
cover: beach-riders
excerpt: Create a responsive image mosaic, using CSS grid to create a grid layout and media queries to adjust the layout for different screen sizes.
listed: true
dateModified: 2024-09-03
---

CSS grid is one of the most powerful tools in modern web design. It allows you to create complex layouts with ease, making it perfect for creating image mosaics.

By using `display: grid`, you can create a responsive grid layout that adapts to different screen sizes. You can also use `grid-row: span 2 / auto` and `grid-column: span 2 / auto` to create items that span two rows or two columns respectively. Finally, you can wrap these styles in a media query to avoid applying them on small screen sizes, making sure your layout looks great on all devices.

https://codepen.io/chalarangelo/pen/XWvbQjz

```css
.image-mosaic {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  grid-auto-rows: 240px;
}

@media screen and (min-width: 600px) {
  .card-tall {
    grid-row: span 2 / auto;
  }

  .card-wide {
    grid-column: span 2 / auto;
  }
}
```

---

### Snippets Css S Image Overlay Hover (`content/snippets/css/s/image-overlay-hover.md`)

---
title: Image overlay on hover
language: css
tags: [visual,animation]
cover: architectural
excerpt: Learn how to display an image overlay effect on hover using CSS.
listed: true
dateModified: 2024-09-20
---

Creating an image overlay effect on hover is a great way to add a touch of interactivity to your website. This effect can be achieved using pseudo-elements and pseudo-selectors. For the **structure**, you'll use a `<figure>` element containing an `<img>` and a `<figcaption>`.

Using the `::before` and `::after` pseudo-elements, you can create the top and bottom **bars of your overlay**. Set their `opacity`, `transform`, and `transition` to produce the desired effect.

The `<figcaption>` element can be used for the **text of the overlay**. By setting `display: flex`, `flex-direction: column`, and `justify-content: center`, you can center the text into the image.

Finally, by using the `:hover` pseudo-selector, you can update the `opacity` and `transform` of all the elements and display the overlay.

https://codepen.io/chalarangelo/pen/bGXpmJq

```html
<figure class="hover-img">
  <img src="https://picsum.photos/id/200/440/320.jpg"/>
  <figcaption>
    <h3>Lorem <br/>Ipsum</h3>
  </figcaption>
</figure>
```

```css
.hover-img {
  background-color: #000;
  color: #fff;
  display: inline-block;
  margin: 8px;
  max-width: 320px;
  min-width: 240px;
  overflow: hidden;
  position: relative;
  text-align: center;
  width: 100%;
}

.hover-img * {
  box-sizing: border-box;
  transition: all 0.45s ease;
}

.hover-img::before,
.hover-img::after {
  background-color: rgba(0, 0, 0, 0.5);
  border-top: 32px solid rgba(0, 0, 0, 0.5);
  border-bottom: 32px solid rgba(0, 0, 0, 0.5);
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  content: '';
  transition: all 0.3s ease;
  z-index: 1;
  opacity: 0;
  transform: scaleY(2);
}

.hover-img img {
  vertical-align: top;
  max-width: 100%;
  backface-visibility: hidden;
}

.hover-img figcaption {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  align-items: center;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  line-height: 1.1em;
  opacity: 0;
  z-index: 2;
  transition-delay: 0.1s;
  font-size: 24px;
  font-family: sans-serif;
  font-weight: 400;
  letter-spacing: 1px;
  text-transform: uppercase;
}

.hover-img:hover::before,
.hover-img:hover::after {
  transform: scale(1);
  opacity: 1;
}

.hover-img:hover > img {
  opacity: 0.7;
}

.hover-img:hover figcaption {
  opacity: 1;
}
```

---

### Snippets Css S Image Text Overlay (`content/snippets/css/s/image-text-overlay.md`)

---
title: Image with text overlay
language: css
tags: [visual]
cover: icebreaker
excerpt: Learn how to create an image with a text overlay using CSS.
listed: true
dateModified: 2024-09-08
---

Displaying text on top of images, while very common, is fairly tricky to get right. This is mainly because the text needs to be readable regardless of the background image and color. Luckily, creating **text overlays** is easy with CSS.

https://codepen.io/chalarangelo/pen/WNVwQgY

## Using `backdrop-filter`

The most modern technique to create a text overlay is by using the `backdrop-filter` property. This property applies a filter to the backdrop of an element, which is the area behind the element itself.

For an overlay effect, you can use the `blur()` and `brightness()` functions, which apply a blur and adjust the brightness of the backdrop respectively.

```html
<figure>
  <figcaption>Text</figcaption>
  <img src="/path/to/img.jpg">
</figure>
```

```css
figure {
  position: relative;
}

figcaption {
  position: absolute;
  color: #fff;
  backdrop-filter: blur(14px) brightness(75%);
}
```

## Using `linear-gradient`

A more traditional way to achieve a similar effect is by using a `linear-gradient()` background. This method is less flexible than `backdrop-filter`, but it works well in most cases.

Depending on your needs, you can adjust the gradient's colors, direction, and opacity to create different overlay effects.

```html
<figure>
  <img src="/path/to/img.jpg">
  <figcaption>Text</figcaption>
</figure>
```

```css
figure {
  position: relative;
}

figcaption {
  position: absolute;
  color: #fff;
  background: linear-gradient(0deg, #00000088 30%, #ffffff44 100%);
}
```

---

### Snippets Css S Inherited Properties Cheatsheet (`content/snippets/css/s/inherited-properties-cheatsheet.md`)

---
title: CSS inherited properties cheatsheet
shortTitle: Inherited properties
language: css
tags: [layout,cheatsheet]
cover: half-trees
excerpt: A quick reference for inherited CSS properties.
listed: true
dateModified: 2022-11-20
---

The [CSS specification](https://www.w3.org/TR/CSS21/propidx.html) clearly states which CSS properties are inherited but the related appendix is not the most user-friendly resource. Here's a quick reference of the inherited properties that you are most likely to run into:

- `border-collapse`
- `border-spacing`
- `caption-side`
- `color`
- `cursor`
- `direction`
- `empty-cells`
- `font-family`
- `font-size`
- `font-style`
- `font-variant`
- `font-weight`
- `font-size-adjust`
- `font-stretch`
- `font`
- `letter-spacing`
- `line-height`
- `list-style-image`
- `list-style-position`
- `list-style-type`
- `list-style`
- `orphans`
- `quotes`
- `tab-size`
- `text-align`
- `text-align-last`
- `text-decoration-color`
- `text-indent`
- `text-justify`
- `text-shadow`
- `text-transform`
- `visibility`
- `white-space`
- `widows`
- `word-break`
- `word-spacing`
- `word-wrap`

---

### Snippets Css S Input With Prefix (`content/snippets/css/s/input-with-prefix.md`)

---
title: Input with prefix
language: css
tags: [interactivity,visual]
cover: flower-portrait-4
excerpt: Ever wanted to create an input with a visual, non-editable prefix? CSS can help you with that!
listed: true
dateModified: 2024-08-30
---

Ever wanted to create an input with a visual, **non-editable prefix**? HTML, CSS and some ingenuity can help you with that!

All you really need is **two elements**: a `<span>` for the prefix and an `<input>` for the actual input field. By styling them accordingly, you can create a visually appealing input box that looks like it has a prefix.

For starters, you can create a **container element** with `display: flex` to align the prefix and the input field. You can then remove the border and outline from the `<input>` field. Apply them to the parent element instead to make it look like an input box.

Finally, you can use the `:focus-within` pseudo-class selector to style the parent element accordingly, when the user interacts with the `<input>` field.

```html
<div class="input-box">
  <span class="prefix">+30</span>
  <input type="tel" placeholder="210 123 4567"/>
</div>
```

```css
.input-box {
  display: flex;
  align-items: center;
  border: 1px solid #a0a0a0;
  border-radius: 4px;
}

.input-box input {
  border: none;
  outline: none;
}

.input-box:focus-within {
  border-color: #4f7df3;
}
```

https://codepen.io/chalarangelo/pen/gOVpEMK

> [!CAUTION]
>
> Make sure to include your prefix when **submitting the form**. You can use JavaScript to append it to the input value before sending it to the server.

---

### Snippets Css S Inset (`content/snippets/css/s/inset.md`)

---
title: Using the inset CSS property
shortTitle: Using inset
language: css
tags: [layout]
cover: yellow-white-mug-1
excerpt: The inset shorthand property makes element positioning in CSS easier. Learn how to use it effectively in your projects.
listed: true
dateModified: 2025-05-15
---

A few months back, I learned about the `inset` property in CSS and was pleasantly surprised by it. I still haven't seen it in the wild all that much, so I thought I'd take a moment to share some thoughts on it and why I think it's a pretty useful tool to have in your CSS toolbox.

<baseline-support featureId="logical-properties">
</baseline-support>

## What is the `inset` property?

The `inset` property is a **shorthand** for `top`, `right`, `bottom`, and `left`. This can save you from the tedious process of defining these properties individually, as you can use `inset` to set them all at once.

```html
<div class="box"></div>
```

```css
.box {
  position: absolute;
  /* top: 10px, right: 20px, bottom: 30px, left: 40px */
  inset: 10px 20px 30px 40px;
}
```

## Physical, not logical

Although the `inset` property is defined in the [CSS Logical Properties and Values Module](https://drafts.csswg.org/css-logical-1/), it is a **physical property**, not a logical one. This means it always refers to the physical dimensions (`top`, `right`, `bottom`, `left`) of an element, regardless of the writing mode or text direction (e.g., left-to-right or right-to-left).

If you need logical positioning (e.g., `inline-start`, `block-end`), you'll need to use the logical properties instead.

@[Further reading](/css/s/logical-physical-properties-map)

## Positioned elements

One important thing to remember is that the `inset` property only works on **positioned elements**. This means the element must have a `position` value of `absolute`, `relative`, `fixed`, or `sticky`. Without a `position` value, the `inset` property will have no effect.

```html
<div class="box"></div>
```

```css
.box {
  /* The `position` is required for `inset` to work */
  position: absolute;
  /* top: 20px, right: 20px, bottom: 20px, left: 20px */
  inset: 20px;
}
```

## Practical use cases

Ok, that's a cool shorthand. _But what can I do with it?_ Here are a few practical use cases for the `inset` property:

### Fill a parent

If you want an element to **fill its parent container**, you can simply use `inset: 0` to set all sides to `0`. This is a concise way to set `top`, `right`, `bottom`, and `left` to `0`.

```html
<div class="parent">
  <div class="child"></div>
</div>
```

```css
.parent {
  position: relative;
}

.child {
  position: absolute;
  /* Fill the parent */
  /* top: 0, right: 0, bottom: 0, left: 0 */
  inset: 0;
}
```

### Center an element

To center an element within its parent, you can use one or more values of the `inset` property to position it. If, for example, the element is half the size of its parent, you can use `inset: 25%` to center it.

```html
<div class="parent">
  <div class="child"></div>
</div>
```

```css
.parent {
  position: relative;
  width: 200px;
  height: 200px;
}

.child {
  position: absolute;
  /* Center the child */
  /* top: 25%, right: 25%, bottom: 25%, left: 25% */
  inset: 25%;
  width: 100px;
  height: 100px;
}
```

### Custom positioning

You can also use `inset` with custom values to position an element in a specific way, leveraging the `auto` keyword to essentially skip some sides. For example, if you want to position an element `10px` from the `top` and `50px` from the `left`, you can skip the two values in the middle.

```html
<div class="box"></div>
```

```css
.box {
  position: absolute;
  /* top: 10px, left: 50px */
  inset: 10px auto auto 50px;
}
```

## Conclusion

The `inset` property is a handy shorthand for positioning elements in CSS. By combining it with the appropriate `position` value, you can simplify your code and achieve a variety of layouts, from filling a parent container to centering elements or creating custom positions. Just keep in mind that it is a **physical property**, so it won't adapt to writing modes or text directions.

---

### Snippets Css S Isometric Card (`content/snippets/css/s/isometric-card.md`)

---
title: Isometric card
language: css
tags: [visual]
cover: guitar-living-room
excerpt: Learn how to create an isometric card using CSS.
listed: true
dateModified: 2024-09-10
---

The **isometric perspective** is a type of axonometric projection that represents three-dimensional objects in two dimensions. It harkens back to the days of early video games and is a fun way to create a unique visual effect on the web.

All you need to do is use `transform` with `rotateX()` and `rotateY()` to create the isometric effect. Using `transform-style: preserve-3d` ensures that the card maintains its **3D perspective**. Moreover, you can add `will-change: transform` to **optimize the card for animations**.

You can also add a `box-shadow` to give the card a more realistic look. Finally, use `transition` to animate the card, creating a lift effect when the user hovers over it.

https://codepen.io/chalarangelo/pen/BaXKKpj

```css
.isometric-card {
  transform: rotateX(51deg) rotateZ(43deg);
  transform-style: preserve-3d;
  will-change: transform;
  box-shadow: 1px 1px 0 1px #f9f9fb, -1px 0 28px 0 rgba(34, 33, 81, 0.01),
    28px 28px 28px 0 rgba(34, 33, 81, 0.25);
  transition: 0.4s ease-in-out transform, 0.3s ease-in-out box-shadow;
}

.isometric-card:hover {
  transform: translate3d(0px, -16px, 0px) rotateX(51deg) rotateZ(43deg);
  box-shadow: 1px 1px 0 1px #f9f9fb, -1px 0 28px 0 rgba(34, 33, 81, 0.01),
    54px 54px 28px -10px rgba(34, 33, 81, 0.15);
}
```

---

### Snippets Css S Loaders (`content/snippets/css/s/loaders.md`)

---
title: Animated CSS loaders
shortTitle: Animated loaders
language: css
tags: [animation]
cover: digital-nomad-12
excerpt: Loading indicators are a staple of modern web design. Here are some CSS loaders to keep your users engaged while they wait.
listed: true
dateModified: 2024-09-02
---

Loading indicators are a staple of modern web design. They keep users engaged while they wait for content to load. With a little HTML and CSS, you can create a variety of loaders to suit your needs. Here are some examples to get you started.

https://codepen.io/chalarangelo/pen/LYwVvVr

## Bouncing ball loader

For the bouncing loader, you'll need a parent with three elements, one for each ball. Use `@keyframes` to define a bouncing animation, using the `opacity` and `transform` properties. Use a single axis translation on `transform: translate3d()` to achieve better animation performance.

For the container, you'll have to set `display: flex` and `justify-content: center` to position the balls in the center. Give each ball the same `width` and `height` and `border-radius: 50%` to make them circular. Apply the animation to each ball, using a different `animation-delay` for each and `animation-direction: alternate` to create the appropriate effect.

```html
<div class="bouncing-loader">
  <div></div>
  <div></div>
  <div></div>
</div>
```

```css
.bouncing-loader {
  display: flex;
  justify-content: center;
}

.bouncing-loader > div {
  width: 16px;
  height: 16px;
  margin: 3rem 0.2rem;
  background: #8385aa;
  border-radius: 50%;
  animation: bouncing-loader 0.6s infinite alternate;
}

.bouncing-loader > div:nth-child(2) {
  animation-delay: 0.2s;
}

.bouncing-loader > div:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes bouncing-loader {
  to {
    opacity: 0.1;
    transform: translate3d(0, -16px, 0);
  }
}
```

## Pulse loader

For the pulse loader, you'll need a parent container with two child elements, one for each ring. Use `@keyframes` to define an animation at two points in the cycle. At the start (`0%`), the two child elements have no `width` or `height` and are positioned at the center. At the end (`100%`), both elements have increased `width` and `height`, but their `position` is reset to `0`. Use `opacity` to transition from `1` to `0` when animating to give them a disappearing effect as they expand.

Set a predefined `width` and `height` for the parent container and use `position: relative` to position its children. Use `animation-delay` on the second child, so that each element starts its animation at a different time.

```html
<div class="ripple-loader">
  <div></div>
  <div></div>
</div>
```

```css
.ripple-loader {
  position: relative;
  width: 64px;
  height: 64px;
}

.ripple-loader div {
  position: absolute;
  border: 4px solid #454ADE;
  border-radius: 50%;
  animation: ripple-loader 1s ease-out infinite;
}

.ripple-loader div:nth-child(2) {
  animation-delay: -0.5s;
}

@keyframes ripple-loader {
  0% {
    top: 32px;
    left: 32px;
    width: 0;
    height: 0;
    opacity: 1;
  }
  100% {
    top: 0;
    left: 0;
    width: 64px;
    height: 64px;
    opacity: 0;
  }
}
```

## Donut spinner

For the donut spinner, you'll need a single element. Use a semi-transparent `border` for the whole element. Exclude one side that will serve as the loading indicator for the donut. Define and use an appropriate animation, using `transform: rotate()` to rotate the element.

```html
<div class="donut"></div>
```

```css
.donut {
  display: inline-block;
  border: 4px solid rgba(0, 0, 0, 0.1);
  border-left-color: #7983ff;
  border-radius: 50%;
  width: 30px;
  height: 30px;
  animation: donut-spin 1.2s linear infinite;
}

@keyframes donut-spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
```

---

### Snippets Css S Logical Physical Properties Map (`content/snippets/css/s/logical-physical-properties-map.md`)

---
title: Logical and physical CSS properties equivalents
shortTitle: Logical & physical properties
language: css
tags: [layout]
cover: succulent-8
excerpt: New to CSS logical properties? This article provides a handy map of logical properties to their physical counterparts.
listed: true
dateModified: 2025-05-12
---

Logical properties are a new way to define layout in CSS. They allow you to use logical directions (like `start`, `end`, `inline`, and `block`) instead of physical directions (like `left`, `right`, `top`, and `bottom`). This makes it easier to create layouts that work well in different writing modes and languages.

<baseline-support featureId="logical-properties">
</baseline-support>

However, if you're used to working with physical properties, it can be a bit confusing at first. To help you out, here's a handy map of logical properties to their physical counterparts!

> [!NOTE]
>
> In this article, I'm assuming you're writing from left to right (LTR). If you're writing from right to left (RTL), the physical properties will be reversed. For example, `left` becomes `right`, and `top` becomes `bottom`.

### Position

| Logical Property | Physical Property (LTR) |
|------------------|-------------------|
| `inset-inline-start` | `left` |
| `inset-inline-end` | `right` |
| `inset-inline` | `left` & `right` |
| `inset-block-start` | `top` |
| `inset-block-end` | `bottom` |
| `inset-block` | `top` & `bottom` |

## Margin

| Logical Property | Physical Property (LTR) |
|------------------|-------------------|
| `margin-inline-start` | `margin-left` |
| `margin-inline-end` | `margin-right` |
| `margin-inline` | `margin-left` & `margin-right` |
| `margin-block-start` | `margin-top` |
| `margin-block-end` | `margin-bottom` |
| `margin-block` | `margin-top` & `margin-bottom` |

## Padding

| Logical Property | Physical Property (LTR) |
|------------------|-------------------|
| `padding-inline-start` | `padding-left` |
| `padding-inline-end` | `padding-right` |
| `padding-inline` | `padding-left` & `padding-right` |
| `padding-block-start` | `padding-top` |
| `padding-block-end` | `padding-bottom` |
| `padding-block` | `padding-top` & `padding-bottom` |

## Border

| Logical Property | Physical Property (LTR) |
|------------------|-------------------|
| `border-inline-start` | `border-left` |
| `border-inline-start-color` | `border-left-color` |
| `border-inline-start-style` | `border-left-color` |
| `border-inline-start-width` | `border-left-color` |
| `border-inline-end` | `border-right` |
| `border-inline-end-color` | `border-right-color` |
| `border-inline-end-style` | `border-right-color` |
| `border-inline-end-width` | `border-right-color` |
| `border-inline` | `border-left` & `border-right` |
| `border-inline-color` | `border-left-color` & `border-right-color` |
| `border-inline-style` | `border-left-style` & `border-right-style` |
| `border-inline-width` | `border-left-width` & `border-right-width` |
| `border-block-start` | `border-top` |
| `border-block-start-color` | `border-top-color` |
| `border-block-start-style` | `border-top-color` |
| `border-block-start-width` | `border-top-color` |
| `border-block-end` | `border-bottom` |
| `border-block-end-color` | `border-bottom-color` |
| `border-block-end-style` | `border-bottom-color` |
| `border-block-end-width` | `border-bottom-color` |
| `border-block` | `border-top` & `border-bottom` |
| `border-block-color` | `border-top-color` & `border-bottom-color` |
| `border-block-style` | `border-top-style` & `border-bottom-style` |
| `border-block-width` | `border-top-width` & `border-bottom-width` |
| `border-start-start-radius` | `border-top-left-radius` |
| `border-start-end-radius` | `border-top-right-radius` |
| `border-end-start-radius` | `border-bottom-left-radius` |
| `border-end-end-radius` | `border-bottom-right-radius` |

## Size

| Logical Property | Physical Property (LTR) |
|------------------|-------------------|
| `inline-size` | `width` |
| `min-inline-size` | `min-width` |
| `max-inline-size` | `max-width` |
| `block-size` | `height` |
| `min-block-size` | `min-height` |
| `max-block-size` | `max-height` |

## Values

In addition to logical properties, there are also logical values that can be used in a variety of other CSS properties.

| Logical Value | Physical Value (LTR) |
|------------------|-------------------|
| `start` | `left` |
| `end` | `right` |
| `inline-start` | `left` |
| `inline-end` | `right` |
| `block-start` | `top` |
| `block-end` | `bottom` |

---

### Snippets Css S Mouse Cursor Gradient Tracking (`content/snippets/css/s/mouse-cursor-gradient-tracking.md`)

---
title: Mouse cursor gradient tracking
language: css
tags: [visual,interactivity]
cover: tram-car
excerpt: Create a hover effect where the gradient follows the mouse cursor, with CSS and a little bit of JavaScript.
listed: true
dateModified: 2024-08-23
---

Cursor tracking effects are cool, but they're generally tricky to implement. Luckily, [CSS custom properties](/css/s/variables) make implementation much easier, especially when combined with some simple JavaScript for DOM manipulation.

## HTML structure

For this example, we'll use a `<button>` element with a `<span>` child. The `<span>` will contain the **text** that will be hovered over. The `<span>` is necessary, as we will be applying the gradient to the button's `::before` **pseudo-element**. Without the `<span>`, the gradient would be applied on top of the button's text, making it hard to read.

```html
<button class="mouse-cursor-gradient-tracking">
  <span>Hover me</span>
</button>
```

## The gradient's CSS

As mentioned previously, we'll use the `::before` pseudo-element to create the gradient. For this to work, we'll have to use `position: absolute` on the pseudo-element, which means the `<button>` itself needs to have `position: relative`.

We'll also need to declare two **CSS variables**, `--x` and `--y`, to track the position of the mouse on the button. We'll make sure to update these with JavaScript a little bit later.

Finally, we'll use a third CSS variable, `--size`, to modify the gradient's dimensions. This variable, will start at `0`, and will be updated to `200px` when the button is hovered over.

```css
.mouse-cursor-gradient-tracking {
  position: relative;
  background: #7983ff;
  overflow: hidden;
}

.mouse-cursor-gradient-tracking span {
  position: relative;
  pointer-events: none;
}

.mouse-cursor-gradient-tracking::before {
  --size: 0;
  content: '';
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--size);
  height: var(--size);
  background: radial-gradient(circle closest-side, pink, transparent);
  transform: translate(-50%, -50%);
  transition: width 0.2s ease, height 0.2s ease;
}

.mouse-cursor-gradient-tracking:hover::before {
  --size: 200px;
}
```

## Tracking the mouse with JavaScript

Tracking the mouse is relatively simple. We'll first use `Document.querySelector()` to select the button, and then use `EventTarget.addEventListener()` to register a handler for the `'mousemove'` event.

Every time the **listener is triggered**, we'll use `Element.getBoundingClientRect()` to get the button's position on the screen, and then use `CSSStyleDeclaration.setProperty()` to update the values of the `--x` and `--y` CSS variables.

When these two values change, **the gradient will update its position**, making it look like it's following the mouse cursor.

```js
const btn = document.querySelector('.mouse-cursor-gradient-tracking');

btn.addEventListener('mousemove', e => {
  const rect = e.target.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;

  btn.style.setProperty('--x', x + 'px');
  btn.style.setProperty('--y', y + 'px');
});
```

Putting everything together gives us a nice hover effect where the gradient follows the mouse cursor. You can see the final result in the CodePen below:

https://codepen.io/chalarangelo/pen/MWMdMaX

---

### Snippets Css S Navigation List Item Hover And Focus Effect (`content/snippets/css/s/navigation-list-item-hover-and-focus-effect.md`)

---
title: Navigation list item hover & focus effect
language: css
tags: [visual]
cover: cloudy-rock-formation
excerpt: Create a custom hover and focus effect for navigation items, using CSS transformations.
listed: true
dateModified: 2024-09-19
---

Creating a custom hover and focus effect for navigation items is a fairly straightforward task, leveraging the power of **CSS transformations**.

For each element in your navigation list, you can use the `::before` pseudo-element to create a hover effect. By default, you can hide it using `transform: scale(0)`. Then, using the `:hover` and `:focus` pseudo-class selectors, you can transition the pseudo-element to `transform: scale(1)` and show its colored background.

Finally, prevent the pseudo-element from covering the anchor element using `z-index`.

```css
li a {
  position: relative;
  display: block;
  z-index: 0;
}

li a::before {
  position: absolute;
  content: "";
  width: 100%;
  height: 100%;
  bottom: 0;
  left: 0;
  background-color: #2683f6;
  z-index: -1;
  transform: scale(0);
  transition: transform 0.5s ease-in-out;
}

li a:hover::before,
li a:focus::before {
  transform: scale(1);
}
```

---

### Snippets Css S Nested Border Radius (`content/snippets/css/s/nested-border-radius.md`)

---
title: Perfect nested border radius in CSS
language: css
tags: [visual]
cover: rocky-beach-waves
excerpt: Nesting elements with rounded borders can look very wrong if not done correctly. Here's a quick tip on how to do it right.
listed: true
dateModified: 2022-04-03
---

Nesting elements with rounded borders can look very wrong if not done correctly. Luckily, there's a simple math trick to make it look right. All you need to do is **calculate the border radius of one of the elements and the distance between them**.

## The formula

The border radius of the outer element should be equal to the sum of the border radius of the inner element and the distance between the two elements. This can be mathematically expressed as `innerRadius + distance = outerRadius` or more tersely `R1 + D = R2`.



### Example

Let's take a look at a simple CSS example. Say we want to style two nested boxes with rounded borders. The outer box has a `border-radius` of `24px` and a `padding` of `8px`. Using the previous formula, we can deduce that the inner box should have a `border-radius` of `16px`.

```css
.outer {
  border-radius: 24px;
  padding: 8px;
}

.inner {
  border-radius: 16px;
}
```

## CSS Variables

Using **CSS variables**, we can make this even easier. Depending on the use case, we can adapt the formula to calculate the outer radius from the inner one, or the other way around. Here's an example of how to do this:

<code-tabs>

```css title="From outer radius"
.outer {
  --outer-radius: 24px;
  --padding: 8px;
  --inner-radius: calc(var(--outer-radius) - var(--padding));

  border-radius: var(--outer-radius);
  padding: var(--padding);
}

.inner {
  border-radius: var(--inner-radius);
}
```

```css title="From inner radius"
.outer {
  --inner-radius: 16px;
  --padding: 8px;
  --outer-radius: calc(var(--inner-radius) + var(--padding));

  border-radius: var(--outer-radius);
  padding: var(--padding);
}

.inner {
  border-radius: var(--inner-radius);
}
```

</code-tabs>

> [!NOTE]
>
> This setup may work for simple cases, but if you have multiple `padding` or `margin` values, you may need to tweak it to fit your needs.

---

### Snippets Css S Offscreen (`content/snippets/css/s/offscreen.md`)

---
title: Hide element offscreen
shortTitle: Offscreen
language: css
tags: [layout,visual]
cover: succulent-2
excerpt: Hide an element completely (visually and positionally) in the DOM while still allowing it to be accessible.
listed: true
dateModified: 2024-09-02
---

Hiding an element with `display: none` comes with the drawback of making it inaccessible to screen readers. On the other hand, **hiding an element offscreen** allows you to keep it **accessible** while removing it from the visual and positional flow of the page. This is useful for elements that are not meant to be seen but are still required for accessibility purposes (e.g. skip links).

In order to achieve this, you will need to create your own **utility class** that will hide the element offscreen. This class will remove any `border` and `padding`, hide the element's `overflow`, and make the `height` and `width` of the element `1px` while negating them using `margin`. The element will also be positioned absolutely so that it does not take up space in the DOM.

> [!TIP]
>
> This technique provides an **accessible** and layout-friendly alternative to `display: none` (not readable by screen readers) and `visibility: hidden` (takes up physical space in the DOM).

```html
<a class="button" href="https://google.com">
  Learn More <span class="offscreen"> about pants</span>
</a>
```

```css
.offscreen {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
}
```

---

### Snippets Css S Perfect Transition Duration (`content/snippets/css/s/perfect-transition-duration.md`)

---
title: The perfect duration for CSS transitions
shortTitle: CSS transition duration
language: css
tags: [interactivity,visual,animation]
cover: perfect-timing
excerpt: Learn how to make your CSS transitions feel perfect when users interact with elements on the page with this simple tip.
listed: true
dateModified: 2021-06-12
---

We have all experienced a website interaction that feels sluggish or otherwise off on account of poor transition or animation duration and timing. However, there is a very simple "golden rule" to help you avoid this poor user experience, called **Doherty Threshold:**

> Productivity soars when a computer and its users interact at a pace (<400ms) that ensures that neither has to wait on the other.

The **magic number** of `0.4s` sounds like a very reasonable threshold, but take a look at any of your favorite websites and you'll notice that most `transition-duration` or `animation-duration` values are closer to `0.3s`. This might have something to do with our more recent expectation of what a fast interaction should feel like - after all the relevant research paper was published in 1982.

---

### Snippets Css S Print Stylesheet (`content/snippets/css/s/print-stylesheet.md`)

---
title: CSS Print Stylesheet
language: css
tags: [visual]
cover: cozy-desk-setup
excerpt: A short opinionated print stylesheet to make your websites look great on paper.
listed: true
dateModified: 2023-06-11
---

While it's not that often we physically print content from the web, **print stylesheets shouldn't be overlooked**. They can be used to make sure that the content of your website is presented in a legible and print-friendly manner. Here's a simple, opinionated print stylesheet that you can use as a base for your own:

```css
@media print {
  @page {
    size: A4;
  }

  body {
    margin: 0;
    padding: 0;
  }

  body, p, h1, h2, h3, h4, h5, h6, li {
    font-family: Helvetica, Arial, sans-serif;
    font-size: 12pt;
    font-weight: normal;
  }

  h1, h2, h3, h4, h5, h6 {
    font-weight: bold;
    margin-bottom: 0.5em;
  }

  h1 {
    font-size: 24pt;
  }

  h2 {
    font-size: 18pt;
  }

  h3 {
    font-size: 14pt;
  }

  a:any-link {
    color: #0000FF;
    text-decoration: none;
  }

  a:any-link::after {
    content: " [" attr(href) "] ";
  }

  img {
    width: 100%;
  }

  header, footer, nav, aside, form, iframe, script {
    display: none;
  }
}
```

- `@media print` - The print media query is used to apply styles when the page is printed.
- `@page`:
  - `size` - Specify the page size.
- `body`:
  - `margin` - Remove the default margin.
  - `padding` - Remove the default padding.
- `body, p, h1, h2, h3, h4, h5, h6, li`:
  - `font-family` - Use print-friendly fonts.
  - `font-size` - Use a legible font size.
  - `font-weight` - Reset the font weight.
- `h1, h2, h3, h4, h5, h6`:
  - `font-weight` - Make headings bold.
  - `margin-bottom` - Add some space between headings and the content below them.
- `h1`-`h3`:
  - `font-size` - Use a larger font size for headings.
- `a:any-link`:
  - `color` - Use a print-friendly color for links.
  - `text-decoration` - Remove the underline from links.
- `a:any-link::after`:
  - `content` - Add the link URL after the link.
- `img`:
  - `width` - Make images fill the page width.
- `header, footer, nav, aside, form, iframe, script`:
  - `display` - Remove unnecessary elements from the page.

---

### Snippets Css S Pseudo Classes (`content/snippets/css/s/pseudo-classes.md`)

---
title: What are CSS pseudo-classes?
shortTitle: CSS pseudo-classes
language: css
tags: [webdev]
cover: orange-flower
excerpt: Learn how to use CSS pseudo-classes to style an element based on changes to its state.
listed: true
dateModified: 2021-11-07
---

CSS pseudo-classes provide a way to style elements, based on changes to their state. For example, `:hover` can be used to apply additional styles to an element when the user's pointer hovers over it.

Pseudo-classes let you apply styles to elements in relation to the content of the document tree (e.g. `:first-child`), external factors such as the history of the navigator (e.g. `:visited`), the status of their content (e.g. `:checked`) or the position of the mouse (e.g. `:hover`).

## Commonly used pseudo-classes

Below is a list of the top 5 most commonly used pseudo-classes and their usage. This list is by no means complete. You should always refer to relevant documentation from authoritative sources, such as [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes) for more information.

- `:hover`, `:focus` and `:active` are used to provide feedback for user interaction (e.g. changing a button's color on hover)
- `:link` and `:visited` are useful for styling links based on navigation history (e.g. changing the color of visited links)
- `:first-child`, `:last-child`, `:nth-child()` and `:nth-last-child()` are useful when working with collections of elements
- `:not()` is used to match everything except the given selector and can be useful in styling hard to select elements
- `:lang()` allows you to apply special styles based on the language of the document and is useful for multilingual websites

---

### Snippets Css S Reset All Styles (`content/snippets/css/s/reset-all-styles.md`)

---
title: How can I reset all styles on an element?
shortTitle: Reset all styles
language: css
tags: [visual]
cover: rocky-beach-2
excerpt: Have you ever needed to reset all styles previously applied to a selector to their default values? CSS has the tool just for you!
listed: true
dateModified: 2024-08-15
---

Have you ever needed to **reset all styles** previously applied to a selector to their default values? CSS has the tool just for you!

One of the lesser-known properties in CSS is the `all` property. This property is used to set properties to their initial or inherited values, or to the values specified in another cascade layer or stylesheet origin.

Using it in conjunction with the `initial` value, you can reset all styles to their **default values**. However, this will not affect the `direction` and `unicode-bidi` properties, as well as any custom properties you may have defined.

```css
.reset-all-styles {
  all: initial;
}
```

After resetting all styles, you can then apply new styles to the element as needed. Remember that this property should come **first in the CSS rule** to ensure that it takes precedence over any other styles applied to the element.

---

### Snippets Css S Reset (`content/snippets/css/s/reset.md`)

---
title: CSS Reset
language: css
tags: [visual]
cover: bridge-over-road
excerpt: A short, opinionated CSS reset to make your websites look great everywhere.
listed: true
dateModified: 2022-10-16
---

Browsers nowadays are much better at presenting HTML in a consistent manner, making CSS resets of the past largely unnecessary. However, **default browser styles are not particularly great** in most cases, which is why there are tons of CSS resets out there. Drawing inspiration from some of them, here's my opinionated CSS reset, along with an explanation of why I chose to include each rule.

```css
html {
  max-width: 70ch;
  margin: auto;
  padding: 3em 1em;
  font-family: system-ui, 'Segoe UI', Roboto, Cantarell,
    'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-size: 1.25em;
  line-height: 1.75;
}

body {
  margin: 0;
}

h1, h2, h3, h4, h5, h6 {
  margin: 3em 0 1em;
}

p, ul, ol {
  margin-bottom: 2em;
  color: #1d1d1d;
}

small {
  font-size: 80%;
}

sub, sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

pre, code, kbd {
  font-family: monospace, monospace;
  font-size: 1em;
}
```

- `html`:
  - `max-width` - Use `ch` units to set the maximum width in the middle of the optimal readable range (60-80 characters).
  - `margin` - Center content on the page.
  - `padding` - Prevent edge-to-edge text on smaller viewports.
  - `font-family` - Use the [system font stack](/css/s/system-font-stack) to ensure the best possible font rendering. `system-ui` is a new generic font family that replaces `-apple-system` and `BlinkMacSystemFont`.
  - `font-size` - Use a larger font size for better readability and to keep up with recent design trends.
  - `line-height` - Use a larger line height to increase visual clarity.
- `body`:
  - `margin` - Remove the default margin in all browsers.
- `h1`-`h6`:
  - `margin` - Use larger margins for headers to improve visual hierarchy.
- `p`, `ul`, `ol`:
  - `margin-bottom` - Add spacing between textual elements.
  - `color` - Soften text color to improve readability.
- `small`:
  - `font-size` - Correct font size discrepancies between browsers.
- `sub`, `sup`:
  - `font-size` - Correct font size discrepancies between browsers.
  - `line-height` - Prevent elements from affecting line height.
  - `position` - Position elements relative to parent.
  - `vertical-align` - Align elements to the baseline.
  - `bottom`, `top` - Correctly position elements.
- `pre`, `code`, `kbd`:
  - `font-family` - Use monospace fonts for code elements.
  - `font-size` - Correct font size discrepancies between browsers.

---

### Snippets Css S Responsive Favicon Dark Mode (`content/snippets/css/s/responsive-favicon-dark-mode.md`)

---
title: How can I create a custom responsive favicon for dark mode?
shortTitle: Responsive dark mode favicon
language: css
tags: [visual]
cover: dark-mode
excerpt: Learn how to create a custom responsive favicon that can adapt its color palette for dark mode with this quick guide.
listed: true
dateModified: 2021-09-28
---

The rise of dark mode in recent years has made many website favicons feel awkward or even impossible to see in some cases. Provided you have the appropriate assets, it's relatively easy to create a responsive favicon that can **adapt to the user's color scheme preferences**.

In order to create a responsive favicon, you need an **SVG icon** with as few colors as possible and two color palettes, one for light mode and one for dark mode. Usual rules about icon clarity and complexity apply, so make sure your icon meets all the necessary criteria to be visually distinguishable in any scenario. In our example, we will be using a monochrome icon from the fantastic [Feather icon set](https://feathericons.com/).

Leveraging embedded styles in SVG images and the `prefers-color-scheme` media query, we can create an appropriate `<g>` element to group all the elements of the icon. Then, using the `id` of the group, we can apply the color palette for each design. Here's what our final SVG asset looks like:

```html
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
  <style>
    #icon {
      stroke: #000;
      stroke-width: 2px;
      stroke-linecap: round;
      stroke-linejoin: round;
      fill: none;
    }

    @media (prefers-color-scheme: dark) {
      #icon {
        stroke: #fff;
      }
    }
  </style>
  <g id="icon">
    <path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path>
    <line x1="3" y1="6" x2="21" y2="6"></line>
    <path d="M16 10a4 4 0 0 1-8 0"></path>
  </g>
</svg>
```

After creating the SVG asset, you need only include the custom SVG favicon in the page's `<head>` element and you're ready to go. Be sure to include a PNG fallback, if possible, with a rendered version of the icon in either palette:

```html
<head>
  
  <link rel="icon" type="image/png" href="favicon.png" >
  
  <link rel="icon" type="image/svg" href="favicon.svg" >
</head>
```

---

### Snippets Css S Root Vs Html (`content/snippets/css/s/root-vs-html.md`)

---
title: What's the difference between :root and html in CSS?
shortTitle: :root vs html
language: css
tags: [layout,selector]
cover: tree-roots
excerpt: The CSS selectors used to target the root element of an HTML share some similarities, but they also have some differences.
listed: true
dateModified: 2022-05-22
---

CSS has two ways to target the root element of an HTML document - the `:root` pseudo-class and the `html` selector. While these are very similar to each other, they have a couple of differences you should know.

## Selector specificity

The `:root` selector has a higher specificity than the `html` selector. This is because `:root` is a pseudo-class selector, while `html` is a type selector.

```css
:root {
  background-color: red;
}

html {
  background-color: blue;
}

/* The HTML document's root element will have a red background-color.  */
```

## Targeting the root element

CSS can be used to style other types of documents, apart from HTML. This is where the `:root` element comes in to play, allowing you to style the root element of a document. This can be especially important when styling SVG documents, where the `html` selector will not work.

---

### Snippets Css S Scroll Progress Bar (`content/snippets/css/s/scroll-progress-bar.md`)

---
title: Scroll progress bar
language: css
tags: [animation,visual]
cover: coworking-space
excerpt: Create a progress bar indicating the scroll percentage of the page, using CSS and JavaScript.
listed: true
dateModified: 2024-09-22
---

Horizontal progress-style scroll bars are a fairly new trend in web design. They are often used in tandem with the traditional vertical scroll bar to provide a visual representation of the user's progress through the page. So how does one go about implementing this feature?

As far as CSS is concerned, there's very little work to be done. The progress bar itself is just a simple `div` element with a fixed position at the top of the page.

The magic happens in the JavaScript, where we use `EventTarget.addEventListener()` to listen for the `scroll` event. Then, we use the `Element.scrollTop` property to determine the **scroll percentage of the document** and apply it to the `width` of the progress bar.

https://codepen.io/chalarangelo/pen/gOVrBVM

```css
#scroll-progress {
  position: fixed;
  top: 0;
  width: 0%;
  height: 4px;
  background: #7983ff;
  z-index: 10000;
}
```

```js
const scrollProgress = document.getElementById('scroll-progress');
const height =
  document.documentElement.scrollHeight - document.documentElement.clientHeight;

window.addEventListener('scroll', () => {
  const scrollTop =
    document.body.scrollTop || document.documentElement.scrollTop;
  scrollProgress.style.width = `${(scrollTop / height) * 100}%`;
});
```

---

### Snippets Css S Scroll Snap (`content/snippets/css/s/scroll-snap.md`)

---
title: Horizontal or vertical scroll snap in CSS
shortTitle: Horizontal or vertical scroll snap
language: css
tags: [interactivity]
cover: purple-sunset-waves
excerpt: Create a horizontally or vertically scrollable container that will snap on elements when scrolling.
listed: true
dateModified: 2024-08-25
---

Scroll snapping in CSS has been around for a while and it's a great way to create modern interactive experiences. It consists of a handful of properties that allow you to create a scrollable container that will snap on elements when scrolling, either horizontally or vertically.

## Scrollable containers that snap

To create a scrollable container that snaps on elements when scrolling, you can use the `scroll-snap-type` property to control the **snap axis**. This property can be set to `x` for horizontal scrolling or `y` for vertical scrolling. You can also set it to `both` to enable both horizontal and vertical scrolling.

> [!TIP]
>
> If you're working with the new [**Logical Properties**](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties) you can use `block` for vertical and `inline` for horizontal. In this article, however, we'll focus on the more traditional `x` and `y` values.

```css
/* Horizontal scroll snap */
.scroll-snap {
  scroll-snap-type: x mandatory;
}

/* Vertical scroll snap */
.scroll-snap {
  scroll-snap-type: y mandatory;
}
```

Apart from the snap axis, you can also specify the **snap strictness** with `scroll-snap-type`. The `mandatory` value will snap the container to the nearest snap point, while `proximity` will snap the container to the nearest snap point if it's close enough (default).

### Snap alignment

You can also control the **alignment of the snap points** using the `scroll-snap-align` property. This property can be set to `start`, `end`, `center`, or `none`.

```css
/* Snap to the start of the element */
.scroll-snap > .element {
  scroll-snap-align: start;
}

/* Snap to the center of the element */
.scroll-snap > .element {
  scroll-snap-align: center;
}

/* Snap to the end of the element */
.scroll-snap > .element {
  scroll-snap-align: end;
}
```

### Scrolling over the edge

To prevent the default browser **behavior when reaching the end of the scrollable container**, you can use the `overscroll-behavior` property. This property can be set to `auto`, `contain`, or `none`.

The `contain` value is usually preferred as it allows the scroll snap to work as expected when scrolling over the edge of the container.

> [!TIP]
>
> Depending on your use case, you can use `overscroll-behavior-x` and `overscroll-behavior-y` to control the behavior on the horizontal and vertical axes respectively, or even `overscroll-behavior-inline` and `overscroll-behavior-block` if you're using Logical Properties.

```css
/* Horizontal scroll snap */
.scroll-snap {
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
}

/* Vertical scroll snap */
.scroll-snap {
  scroll-snap-type: y mandatory;
  overscroll-behavior-y: contain;
}
```

## Horizontal scroll snap

Putting everything together, we can create a horizontally scrollable container that will snap on elements when scrolling. At its core the HTML and CSS is quite simple. All you need to do is set the `scroll-snap-type` to `x mandatory` and the `overscroll-behavior-x` to `contain`. You can decide on the value of your `scroll-snap-align` based on your design.

https://codepen.io/chalarangelo/pen/VwoYQYj

```html
<div class="horizontal-snap">
  
</div>
```

```css
.horizontal-snap {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
}

.horizontal-snap > .element {
  scroll-snap-align: center; /* or start, end */
}
```

## Vertical scroll snap

Similarly, a vertically scrollable container that snaps on elements when scrolling can be created by setting the `scroll-snap-type` to `y mandatory` and the `overscroll-behavior-y` to `contain`. You can again decide the value of `scroll-snap-align` based on your design.

https://codepen.io/chalarangelo/pen/mdNyXVm

```html
<div class="vertical-snap">
  
</div>
```

```css
.vertical-snap {
  overflow-y: auto;
  overscroll-behavior-y: contain;
  scroll-snap-type: y mandatory;
}

.vertical-snap > .element {
  scroll-snap-align: center; /* or start, end */
}
```

---

### Snippets Css S Select Any Link (`content/snippets/css/s/select-any-link.md`)

---
title: Select any link with CSS
shortTitle: "CSS :any-link pseudo-class"
language: css
tags: [visual,interactivity]
cover: round-leaves
excerpt: You can use a CSS pseudo-class selector to style all links in a page, without worrying if they have been visited or not.
listed: true
dateModified: 2022-03-06
---

Styling links with CSS is considered straightforward, with most developers using the `:link` and `:visited` pseudo-classes. While this solution is very common, there's a less verbose alternative in the form of the `:any-link` pseudo-class. This pseudo-class selects all links, regardless of whether they have been visited or not. Thus, it acts as a catch-all for all links on the page.

```css
:any-link {
  color: #0444f6;
}
```

One important note is that using `:any-link` is different to using the `[href]` attribute selector. The `:any-link` pseudo-class does not select empty links, whereas the `[href]` attribute selector does.

---

### Snippets Css S Shake Invalid Input (`content/snippets/css/s/shake-invalid-input.md`)

---
title: Shake input field when invalid, using CSS
shortTitle: Shake on invalid input
language: css
tags: [animation]
cover: perfect-timing
excerpt: Inform your users of invalid input by shaking the input field.
listed: true
dateModified: 2024-08-31
---

One of the most common **feedback** animations is shaking an input field when the user enters invalid data. Luckily, HTML and CSS have just the right tools to help you create this effect.

Starting with the HTML, we'll need to use the [`pattern`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern) attribute to define the **regular expression** which the input's value must match. This will trigger the `:invalid` pseudo-class when the input is invalid.

Then, using `@keyframes`, we can define a **shake animation** that uses the `margin-left` property to move the element back and forth. Finally, we can use the `:invalid` pseudo-class to apply the `animation` to the element, making it shake when the user enters invalid data.

```html
 <input type="text" placeholder="Letters only" pattern="[A-Za-z]*" />
```

```css
input:invalid {
  animation: shake 0.2s ease-in-out 0s 2;
}

@keyframes shake {
  0% {
    margin-left: 0rem;
  }
  25% {
    margin-left: 0.5rem;
  }
  75% {
    margin-left: -0.5rem;
  }
  100% {
    margin-left: 0rem;
  }
}
```

https://codepen.io/chalarangelo/pen/dyxorLK

---

### Snippets Css S Shapes (`content/snippets/css/s/shapes.md`)

---
title: Create shapes using CSS
shortTitle: Shapes
language: css
tags: [visual,cheatsheet]
cover: oven-paddle
excerpt: Use CSS to create various shapes like circles, triangles, and squares.
listed: true
dateModified: 2024-05-08
---

CSS can be used to create various shapes like circles, triangles, and squares. While this technique is slowly falling out of favor, it can sometimes be worth using for simple shapes, instead of using SVG or images.

https://codepen.io/chalarangelo/pen/gOJOwwo

## Square & Rectangle

A simple CSS square can be created by setting the `width` and `height` to the same value. Similarly, a rectangle can be created by setting the `width` and `height` to different values.

```html
<div class="square"></div>
<div class="rectangle"></div>
```

```css
.square {
  width: 64px;
  height: 64px;
  background: #5394fd;
}

.rectangle {
  width: 128px;
  height: 64px;
  background: #5394fd;
}
```

## Circle & Ellipse

Using the `border-radius` property, a circle can be created by setting it to `50%`. The `width` and `height` must be the same to create a circle. Subsequently, an ellipse can be created by providing two values for the `border-radius` and setting the `width` and `height` to different values.

```html
<div class="circle"></div>
<div class="ellipse"></div>
```

```css
.circle {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: #5394fd;
}

.ellipse {
  width: 128px;
  height: 64px;
  border-radius: 64px / 32px;
  background: #5394fd;
}
```

## Triangles

A triangle can be created by using three borders. The `border-width` should be the same for all borders, and the opposite side of where the triangle points towards should have the desired `border-color`. The adjacent borders should have a `border-color` of `transparent`.

```html
<div class="triangle-down"></div>
<div class="triangle-up"></div>
<div class="triangle-left"></div>
<div class="triangle-right"></div>
```

```css
.triangle-down {
  width: 0;
  height: 0;
  border-top: 32px solid #5394fd;
  border-left: 32px solid transparent;
  border-right: 32px solid transparent;
}

.triangle-up {
  width: 0;
  height: 0;
  border-bottom: 32px solid #5394fd;
  border-left: 32px solid transparent;
  border-right: 32px solid transparent;
}

.triangle-left {
  width: 0;
  height: 0;
  border-right: 32px solid #5394fd;
  border-top: 32px solid transparent;
  border-bottom: 32px solid transparent;
}

.triangle-right {
  width: 0;
  height: 0;
  border-left: 32px solid #5394fd;
  border-top: 32px solid transparent;
  border-bottom: 32px solid transparent;
}
```

---

### Snippets Css S Sibling Fade (`content/snippets/css/s/sibling-fade.md`)

---
title: Fade out siblings on hover
shortTitle: Sibling fade
language: css
tags: [interactivity]
cover: messy-papers
excerpt: Create a cool effect that fades out the siblings of a hovered item with a few lines of CSS.
listed: true
dateModified: 2024-08-28
---

A classic hove effect is to fade out the siblings of an item when it is hovered. This can be achieved with a few lines of CSS by using the `:hover` and `:not()` pseudo-class selectors.

Simply put, when the mouse is over an element, all its siblings will have their `opacity` set to `0.5`, while the hovered element will remain at `1`. Adding a `transition` to the `opacity` property will create a smooth animation effect.

Using the `:hover` selector, we can target the **parent of the hovered element** and change the `opacity` of all its children except the one being hovered. This technique works because the `:hover` selector is applied to the parent element, as well as the child element.

Alternatively, we can use the new `:has()` pseudo-class in combination with `:hover` to achieve the same effect. The `:has()` pseudo-class allows us to select an element based on whether it has a **descendant that matches** a specific selector.

```css
.element {
  transition: opacity 0.3s;
}

/* Using the :hover and :not() pseudo-class selectors */
.container:hover .element:not(:hover) {
  opacity: 0.5;
}

/* Or using the :has() pseudo-class */
.container:has(.element:hover) .element:not(:hover) {
  opacity: 0.5;
}
```

https://codepen.io/chalarangelo/pen/wvVamWR

---

### Snippets Css S Speech Bubble (`content/snippets/css/s/speech-bubble.md`)

---
title: Speech bubbles with CSS
shortTitle: Speech bubbles
language: css
tags: [visual]
cover: greek-coffee
excerpt: Create a content container with a triangle at the top, much like a speech bubble.
listed: true
dateModified: 2024-09-05
---

Speech bubbles have existed in comics for decades, and they are a great way to visually represent dialogue. You can create a similar effect using CSS, by adding a triangle to the top of a content container.

For this task, you'll leverage the `::before` and `::after` pseudo-elements to create **two triangles**. The colors of the two triangles should be the same as the container's `border-color` and the container's `background-color` respectively.

The `border-width` of the one triangle (`::before`) should be `1px` wider than the other one (`::after`), in order to act as the border. The smaller triangle (`::after`) should be `1px` to the right of the larger triangle (`::before`) to allow for its left border to be shown.

And with that, you have a content container with a triangle at the top, much like a speech bubble. You can tweak this effect to also place the triangle at the bottom, left, or right of the container, or even create a different shape altogether.

```css
.container {
  position: relative;
  background: #ffffff;
  border: 1px solid #dddddd;
}

.container::before,
.container::after {
  content: '';
  position: absolute;
  bottom: 100%;
  left: 19px;
  border: 11px solid transparent;
  border-bottom-color: #dddddd;
}

.container::after {
  left: 20px;
  border: 10px solid transparent;
  border-bottom-color: #ffffff;
}
```

https://codepen.io/chalarangelo/pen/dyxYOap

---

### Snippets Css S Squiggle Link Hover Effect (`content/snippets/css/s/squiggle-link-hover-effect.md`)

---
title: Squiggle link hover effect
language: css
tags: [animation,visual]
cover: dreamy-flowers
excerpt: Create the most whimsical link hover effect with a squiggle animation.
listed: true
dateModified: 2024-09-15
---

One of the tiniest details I see people get creative with is the hover effect on links. One of the most whimsical effects I've seen is the squiggle effect. It's a simple yet effective way to make your links stand out and give them a playful touch, making the experience more memorable.

https://codepen.io/chalarangelo/pen/eYqZRdx

## The SVG image

The technique once again hinges on the use of **SVG images** in CSS. This time around we're going to use them as the `background-image` of the link. The SVG will contain a squiggly path that we'll animate to create the squiggle effect. The animation will be a simple translation of the path along the x-axis, creating the illusion of a squiggle.

As we'll have to **data encode** our SVG, here's what it looks like in plain text:

```html
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 4'>
  <style type='text/css'>
    .squiggle {
      animation: shift .3s linear infinite;
    }
    @keyframes shift {
      from {
        transform: translateX(0);
      } to {
        transform: translateX(-15px);
      }
    }
  </style>
  <path
    fill='none'
    stroke='#0087ca'
    stroke-width='2'
    class='squiggle'
    d='M0,3.5 c 5,0,5,-3,10,-3 s 5,3,10,3 c 5,0,5,-3,10,-3 s 5,3,10,3'
  />
</svg>
```

## Putting it to use

As mentioned already, we'll use `background-image` with the `url()` function to animate the `:hover` state of the link. The SVG will be data encoded in the URL, and we'll set the `background-size` to `auto 4px` to ensure the squiggle path is visible.

However, we'll have to create a **repeating background** for the link to make the squiggle effect work. We'll use a `linear-gradient()` with a `background-size` of `2px 2px` to create a repeating pattern. The `background-position` will be set to `0 100%` to ensure the gradient starts at the bottom of the link.

This way, when not hovered, the link will technically use the gradient as its `background-image`. When hovered, the SVG will replace the gradient, creating the squiggle effect.

```html
<p>The <a class="squiggle" href="#">magnificent octopus</a> swam along gracefully.</p>
```

```css
a.squiggle {
  background-image: linear-gradient(to bottom, #0087ca 0%, #0087ca 100%);
  background-position: 0 100%;
  background-repeat: repeat-x;
  background-size: 2px 2px;
  color: inherit;
  text-decoration: none;
}

a.squiggle:hover {
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 4'%3E%3Cstyle type='text/css'%3E.squiggle{animation:shift .3s linear infinite;}@keyframes shift {from {transform:translateX(0);}to {transform:translateX(-15px);}}%3C/style%3E%3Cpath fill='none' stroke='%230087ca' stroke-width='2' class='squiggle' d='M0,3.5 c 5,0,5,-3,10,-3 s 5,3,10,3 c 5,0,5,-3,10,-3 s 5,3,10,3'/%3E%3C/svg%3E");
  background-size: auto 4px;
}
```

---

### Snippets Css S Staggered Animation (`content/snippets/css/s/staggered-animation.md`)

---
title: Staggered list animation
shortTitle: Staggered animation
language: css
tags: [animation]
cover: aerial-view-port
excerpt: Staggered animations can be used to create a more dynamic user experience. Get creative with your lists!
listed: true
dateModified: 2024-09-01
---

The term **staggered animation** refers to an animation effect where elements are animated one after the other, with a slight delay between each element. This can be used to create a more dynamic user experience, especially when dealing with lists.

For this effect to work, we can leverage [CSS variables](/css/s/variables). Each child element will define a value for a custom property (e.g. `--i`), which will be used to calculate the **delay for the animation**. By setting the `transition-delay` property to a **multiple of this custom property**, we can create a staggered effect.

The rest of the process is pretty standard. We'll use a `transition` to animate the `opacity` and `transform` properties of the list elements. When the animation is toggled (e.g. by opening a menu), the list elements will transition from being transparent (`opacity: 0`) and off-screen (`transform: translateX(100%)`) to being visible (`opacity: 1`) and in their final position (`transform: translateX(0)`).

> [!NOTE]
>
> For this example, we'll use a **checkbox** to toggle the animation. You can use JavaScript or any other method to toggle the animation, based on your needs.

https://codepen.io/chalarangelo/pen/ExqjMBw

```html
<div class="container">
  <input type="checkbox" name="menu" id="menu" class="menu-toggler">
  <label for="menu" class="menu-toggler-label">Menu</label>
  <ul class="stagger-menu">
    <li style="--i: 0">Home</li>
    <li style="--i: 1">Pricing</li>
    <li style="--i: 2">Account</li>
    <li style="--i: 3">Support</li>
    <li style="--i: 4">About</li>
  </ul>
</div>
```

```css
.container {
  overflow-x: hidden;
  width: 100%;
}

.menu-toggler {
  display: none;
}

.stagger-menu li {
  opacity: 0;
  transform: translateX(100%);
  transition-property: opacity, transform;
  transition-duration: 0.3s;
  transition-timing-function: cubic-bezier(0.750, -0.015, 0.565, 1.055);
}

.menu-toggler:checked ~ .stagger-menu li {
  opacity: 1;
  transform: translateX(0);
  transition-delay: calc(0.055s * var(--i));
}
```

---

### Snippets Css S Style Default Links (`content/snippets/css/s/style-default-links.md`)

---
title: Style links without a class
shortTitle: Style default links
language: css
tags: [visual,interactivity]
cover: citrus-drink
excerpt: A short summary of your story up to 180 characters long.
listed: true
dateModified: 2022-11-23
---

When styling injected or generated HTML content, you might not have access to the classes or IDs of the elements you want to style. This can become especially annoying when dealing with link elements. Luckily, you can use the `:not()` selector with an appropriate attribute selector to check for the absence of a class and style links accordingly.

```css
a[href]:not([class]) {
  color: #0077ff;
  text-decoration: underline;
}
```

As a bonus tip, you can use [the previous tip about selecting any link](/css/s/select-any-link) to further enhance this solution.

---

--- METRICS ---
- Files Extracted: 101
- Estimated Token Budget: ~83456 tokens
- Canonical Reference: https://codewiki.google/github.com/Chalarangelo/30-seconds-of-code
