Content Developer II at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
132446 stories
·
29 followers

Open Source in 2025: What Will Matter Most This Year?

1 Share
Neon sign reading "Open and awesome." To navigate the pressures on the open source community will take global, collaborative effort, writes Amanda Brock, CEO of OpenUK.

2024 didn’t fail to disappoint in delivering another roller coaster of a year for open source software. Open source isn’t just code today, it’s a philosophy that we have seen put under serious pressure throughout the last couple of years.

Navigating that pressure must become our collective, global, community mission in 2025. That’s the only way that we can hope to protect the future of open source. At the State of Open Con, on Feb. 4 and 5, our agenda will focus on the topics we know will rise in importance in 2025 based on the events of 2024.

Here are some of the ideas we’ll explore during the event:

The Open Source AI Definition Debate

AI has created a fundamental challenge to everything we thought we knew about openness. My stance on the new Open Source AI Definition is clear. The Open Source Initiative’s purpose is to protect the open source definition and open source software. At this juncture, a strong and focused OSI is critical to the future of open source.  We must see the OSI focus its energy not on a second definition but on the future of open source software in 2025.

As we see the Free Software Foundation provide a second definition for openness in AI in 2025, and policymakers grapple more fully with openness at the French-led third AI Action Summit (in Paris, Feb. 10 and 11), I expect the fuss around open source and AI will die down a little. We are likely to see the approach shift to a more pragmatic one.

For example, I believe the debate around AI will shift to focus on disaggregating its component parts — weights, models, algorithms, datasets etc. — and to assess the level of openness of these, whether they are fully or partially open and what the impact of each is.

For open source software, our long-established Open Source Definition being observed in licensing in AI will be critical as it ensures the free flow that underlies open source — allowing anyone to use it without restriction other than legal restriction.

By assessing risk from this perspective, the outcomes will be far less tied to the current state of AI. Trying to define a sector could only ever be a transient approach for AI and open source. A clearer understanding of what it means for data to be open (or partially open), both inside and outside of the context of AI, will be high up the policy agenda in 2025 and beyond.

Agentic AI is the current flavor of the month. Few beyond AutoGPT are really delivering on this promise to date. We will see this area of AI evolve and stay current throughout 2025. Of course, other areas will also evolve in relatively quick succession. as AI continues to take shape.

Greener Data Centers

The infrastructure necessary not only for AI but for our digital future is critical. Data centers, GPUs and the infrastructure necessary to service AI, including power, will be a key focus of 2025. The infrastructure required must be minimized and enable greener processing. Open source lends itself well to this.

Greening digital infrastructure will be a key focus of the United Nations Climate Change Conference (UNCCC  COP 30), slated for November in Brazil. And as the Green Digital Declaration and Digital Action Day evolve from COP29, we are likely to see a bigger focus on open technology which can be a huge enabler of this in 2025.

Challenging Open Source Business Models

Open source isn’t a marketing tool. It’s for life.

Despite the normalization in recent years of a pattern of commercial organizations switching away from open source licenses as they grow (and facing the choice of taking more funding or doing an initial public offering), I expect that this bait-and-switch commercial approach is coming to an end.

Key to this is the power of the fork combined with the shift in the open source community to commercial users and their staff. A key user benefit of open source to commercial users is their staff engagement in projects and evolving skills through participation. Powerful users are able to fund and manage the shift back to open source through a fork if a key software project changes its license to a more restrictive one

The path to a successful fork is no longer the nuclear option. Thanks to what James Governor, founder of the tech analyst firm RedMonk, has labeled “The Hammer of Valkey,” 2024’s forks have demonstrated that in open source in 2025, it won’t be the “buyer” but the “seller” who must beware.

Geopolitical Tremors: Shaking the Foundations of Open Source

We confronted a harsh reality in 2024, with the first exclusions of developers from open source projects as a consequence of geopolitics and sanctions.

This risks the opening of a floodgate that might destroy the essence of the global collaboration on which open source is based. Any restrictions need to be carefully understood and managed to protect the heart of our modern digital world, where both AI and open source span geographical borders and require a global approach to governance.

That governance unnecessarily restricting collaboration would be very destructive to the world-leading innovation open source enables. We must educate our policymakers on the importance of this.

The Public Sector and Paying for Open Source

As all governments look to their digital futures, they are scratching their heads, wondering how to become tech-savvy and build forward-looking infrastructure to serve their citizens’ needs. Ultimately, open source will serve this. As a consequence, we will increasingly see public sector engagement in open source at scale through 2025 and beyond.

This offers an opportunity to build funding models that include public sector investment at scale. The opportunity this offers to consciously divert funding from the public purse into sustainable software through open source must not be missed. And there is ample opportunity for that to include the recognition that not only innovation but also maintenance and skills development must be supported.

The Global Challenge

Looking ahead to 2025, open source’s challenges remain complex. We must evolve open source principles to meet AI’s rapid transformation while preserving the collaborative spirit that’s always been our greatest strength. Key to this is the recognition that the future of open source is not simply in the Global North nor in enterprise.

As our global digital infrastructure spans geographical borders, it requires a global governance regime and an understanding of the benefits and funding needs of globally collaborative innovation. This will allow it not only to survive but to thrive.


State of Open Con is a conference covering open technology, including the future of open and technical open source software and security tracks. The event, held Feb. 4-5 in London, includes plenaries on the future of open source and AI openness.

Alex Williams, founder and publisher of The New Stack, will co-host a track on the future of open source at the event.

The post Open Source in 2025: What Will Matter Most This Year? appeared first on The New Stack.

Read the whole story
alvinashcraft
1 hour ago
reply
West Grove, PA
Share this story
Delete

Xbox and LG to Bring Cloud Gaming to LG Smart TVs

1 Share

Xbox and LG to Bring Cloud Gaming to LG Smart TVs

LG Xbox Cloud Gaming Hero Image

We’re focused on bringing more games to more people around the world, on the devices you choose to play with. Millions of Game Pass Ultimate members are already using Xbox Cloud Gaming (Beta) to play great games from the Game Pass catalog on various devices.

Today, we’re excited to announce our collaboration with LG Electronics to bring the Xbox app to their new LG Smart TVs later this year. This means Game Pass Ultimate members will be able to play their games directly from the Xbox app on supported LG Smart TVs via Xbox Cloud Gaming. This gives players even more choice in how they enjoy their favorite games.

Soon, players with LG Smart TVs will be able to explore the Gaming Portal for direct access to hundreds of games in the Game Pass Ultimate catalog, including popular titles such as Call of Duty: Black Ops 6, and upcoming releases like Avowed (launching February 18, 2025).

Stream Your Own Game

We also recently announced that Xbox Game Pass Ultimate members can stream select games they own, outside the Game Pass catalog. This feature will also be available on the Xbox app with LG Smart TVs, allowing Game Pass Ultimate members to stream over 50 games they own, including NBA 2K25, Hogwarts Legacy, and more.

We’ll share more details on the Xbox Cloud Gaming experience with LG TV in the coming months. To learn more about Xbox Cloud Gaming and how you can play across TVs and browsers on supported devices like smartphones, PCs, and tablets, visit xbox.com/cloudgaming.

The post Xbox and LG to Bring Cloud Gaming to LG Smart TVs appeared first on Xbox Wire.

Read the whole story
alvinashcraft
1 hour ago
reply
West Grove, PA
Share this story
Delete

Upcoming CVE for End-of-Life Node.js Versions

1 Share
Read the whole story
alvinashcraft
1 hour ago
reply
West Grove, PA
Share this story
Delete

A guide to understanding program and product managers

1 Share

A Guide To Understanding Program And Product Managers

Product managers are often mislabeled and misunderstood. I would know best: One of my content creation pillars is making sure that the role is more clearly defined. Regardless of my reach though, organizations often mix up a product manager with a developer, designer, tech architect, and most often project manager.

Alongside these, program managers are also confused with product managers. While the role initially sounds the same and feels like a synonym (say, like an “app” or “platform” manager), it differs greatly from a product manager.

So, what are the differences? How can those two roles co-exist in the same organization? How can they support one another? All that, and more, in today’s piece!

Who is a program manager?

A program manager oversees a collection of related projects, ensuring they align with broader strategic business objectives and progress effectively with the right teams and stakeholders. Their work is less about the day-to-day execution of individual tasks and more about the big picture, managing how various projects interconnect and contribute to overarching goals.

A good analogy is when you picture an old-time phone operator in front of the switchboard. Program managers have an eagle-eye view of all the connections and know what to connect to in order to get the call (the complicated project) going.

Key responsibilities of a program manager

The key responsibilities a program manager is responsible for include:

  • Managing a portfolio of projects — Program managers coordinate multiple projects within a program, theme, or strategic item, ensuring they collectively achieve their overarching objectives
  • Ensuring alignment with strategic goals — They act as a bridge between organizational leadership and project teams, aligning initiatives with the company’s vision and priorities. It’s sort of an overwatch making sure that teams don’t lose focus
  • Coordinating across teamsProgram managers manage dependencies, resolve conflicts, and mitigate risks to keep programs on track. They know their org exceptionally and are able to identify teams that are able to help other teams achieve their assignment
  • Driving business impact — They also ensure that all efforts translate into measurable outcomes for the organization. This makes it easier to report an odd backend development piece into perspective, where the value added is clear
  • Reporting — Finally, program managers might be responsible for reporting progress on those highly complicated projects, mostly to make sure there are no showstoppers that are invisible to teams needing specific results

Who is a product manager?

A product manager (PM) leads a product that solves a problem by leveraging a team of talented individuals and technology in order to deliver value. They mostly focus on goals and selecting problems to fix (and their order), which usually extends to working on their solution with the dev team.

Key responsibilities of a product manager

A product manager’s responsibilities differ from a program manager. The most important ones are:

  1. Shaping product vision, strategy, and goals — A product manager needs to have a long-term direction for the product and ensure its lasting, successful market presence
  2. Deciding what to build in the product (what problems to solve) and in which order — Based on the strategic goals, a product manager needs to focus on issues that once solved, will best contribute to said goals
  3. Conducting research that justifies their choices — A PM needs to have data and evidence in order to make and defend one’s decisions
  4. Set and measure the success criteria of the developed items — Only well-selected metrics will accurately measure if a specific dev item contributed to the goals and was actually successful
  5. Listening to users and clients and reacting to feedback — This is the only way to identify problems that users struggle with inside the product
  6. Be on a constant look for new market opportunities — Being the first to market with a successful innovation is usually the “easiest” way to become a market leader
  7. Leading teams without authority — While a product manager is not a line manager of the dev team members, he/she is in a position to inspire, motivate, and drive better morale within the team

Program manager vs. product manager: Key differences

While both roles are essential for organizational success, they contribute to it in different ways. Program managers operate at a higher strategic level, overseeing multiple interconnected projects to ensure they align with broader business objectives and are executed efficiently. They track metrics like project completion rates, cost efficiency, and overall impact, leveraging skills in strategic planning, risk management, and cross-team coordination.

In contrast, product managers concentrate on the lifecycle of a single product or feature, prioritizing user-focused solutions that enhance customer satisfaction and drive business value. Their success is measured through metrics like user adoption, engagement, and feature performance, requiring strong skills in user empathy, product design thinking, and roadmap prioritization. Together, these roles complement each other, balancing strategic alignment with user-centric innovation.

PMs can work in the smallest organizations (even a few people start-ups), but program managers only exist in medium-to-large companies, where the complexities are so great and confusing that you need a dedicated person to make sure everyone can navigate the org chart (in search of project dependencies and partners) effectively. Having program managers also helps create streamlined, focused reporting for high-level management, giving time back to product managers who would otherwise have to create and coordinate that reporting process themselves.

How do program managers and product managers work together?

The relationship between these two roles is a partnership of alignment and coordination. Here’s how they complement each other:

How They Work Together

Aligning on objectives

Program managers ensure that product initiatives fit within the broader strategic framework. Product managers, in turn, focus on aligning their products with user needs and business goals. Thus, a PM may need to check with a program manager if an idea fits the overall direction or assign that idea to a specific goal. This way the program manager can later accurately report it upstairs.

Managing dependencies

Program managers coordinate efforts between multiple product managers to handle interdependencies and ensure smooth execution. As a result, the PM can contact the other person to identify teams that they need to work with in order to successfully complete an initiative.

Consistent communication

Both roles rely on transparent communication to manage expectations, share updates, and keep stakeholders informed. This might come down to a call where a PM works on a project coordinated by a program manager in order to keep things going smoothly. It’s also likely that product management software (i.g., Jira) will report everything that the program manager needs.

As the two positions are vastly different, they only cross when needed. As long as there’s mutual respect and understanding, everything should work out just fine.

Conclusion

While program managers and product managers have distinct responsibilities, their collaboration, even though it only happens when needed, benefits everyone in the loop. Program managers drive alignment across initiatives to ensure strategic goals are met, while product managers focus on creating user-centric solutions that deliver tangible value. All that is in the name of achieving and tracking organization-wide goals.

Hopefully, any confusion you had with the two similarly sounding roles has now been cleared and if you encounter either a product or program manager in your new job, you’ll know what to expect. In the meantime, come back soon for the next blog post release. Thank you for reading!

Featured image source: IconScout

The post A guide to understanding program and product managers appeared first on LogRocket Blog.

Read the whole story
alvinashcraft
1 hour ago
reply
West Grove, PA
Share this story
Delete

C# Lists: What They Are and How to Use Them

1 Share

The C# list offers a powerful, flexible, and intuitive way to work with collections in C#. That’s probably why it’s one of the most well-known and popular ways in C# to handle multiple values.

However, many C# developers barely scratch the surface of what there is to know about this important C# class. As it turns out, there are significant performance and thread-safety implications when using lists. Also, as a C# developer, you must be aware of scenarios in which a list is not the best solution and warrants a different collection type.

In this detailed guide, you’ll learn exactly lists are, how they work, how they differ from arrays and other collection types, and much more.

Let’s dig in. 

What Is a List in C# and How Does It Differ From an Array?

Let’s start by clearing up a potential misconception. Throughout this post, when we say “list” or “C# list,” we are referring to the List<T> class, which lives in the Systems.Collections.Generic namespace.

This clarification is necessary because, in programming, the term “list” can be used in a generic way to refer to any kind of list. For instance, a linked list is a textbook data structure that is a “list,” but that’s not what we’ll be talking about here.

With that out of the way, it’s time for our definition. A C# list is a class that offers a type-safe, dynamic way to handle a collection of values in C#.

When I say it’s type safe, I mean that, like an array, a list doesn’t allow you to mix values of different types. By dynamic I mean that, unlike an array, a list enables you to add or remove elements. Also like an array, you can access the elements of a list by their index.

In a nutshell, we can say that this is the main difference between arrays and lists: arrays are fixed in their size, while lists can grow as you need more items. As you’ll learn later, this difference has important performance implications you need to be aware of.

Key Features of C# List

You’ve just read about two key features of a C# list. The first one is that you can add and remove elements from the list as needed, making C# list a great fit for scenarios in which you don’t know in advance how many elements you’ll have to handle. 

The second one is that, like arrays, a list allows you to access their elements using their zero-based indexes.

What more does the C# list have to offer?

  • Sorting capabilities. You can sort the elements from the list
  • Checking the presence of an item. It’s possible to check whether the list contains a given item.
  • Clearing the list. You can remove all of the elements from the list at once
  • Finding elements. The list class provides several different methods for you to find elements according to various needs
  • Adding and removing elements at a specified index. You can add and remove items not only from the end of the list, but also at any specified position

The list above gives you a little taste of what the C# class has to offer but is not exhaustive. Let’s dive deeper into more C# list features.

Basic Operations with C# Lists

Now it’s time to examine code examples for the capabilities above and more.

How Do I Create a List?

There are several ways to create a list in C#. The most basic one is to use the constructor to initialize an empty list:

// empty list of strings, using explicit type declaration:
List<string> names = new List<string>();

// empty list of integers, using type inference
var numbers = new List<int>();

// empty list of DateTime, using a set assignment
List<DateTime> dates = [];

The last example above uses a feature called target-typed new expression, which was introduced in C# 9.0.

What if, instead of creating an empty list, you want to initialize one already with some elements? That is possible:

// creating a list passing an array as parameter
var integers = new List<int>(new int[] { 1, 2, 3 });

// an easier syntax for initialization
var moreIntegers = new List<int> {  1, 2, 3 };

// creating a range of decimals from one to 100
IEnumerable<decimal> decimals = Enumerable
    .Range(1, 100)
    .Select(i => (decimal)i);

// Using the range to initialize the list
var decimalsList = new List<decimal>(decimals);

You can also easily convert an array—or, more technically, any type that implements the IEnumerable<T> interface—to a list:

IEnumerable<int> intArray = new int[] { 1, 2, 3 };
var intList = intArray.ToList();

C# 12 introduced a new feature called collection expressions, which allows you to initialize lists and other types of collection in a more intuitive and cleaner way:

List<int> someMoreIntegers = [1, 2, 3, 5];

Finally, there’s another way to create an empty list that you must be aware of. If you know in advance the maximum number of items your list will reach, then pass that number as the capacity in the constructor:

var emptyListWithCapacityOf20 = new List<int>(20);

We’ll explain why that’s important in more detail later. For now, just know that informing the capacity can improve your application’s performance.

How Can I Add or Remove Elements from a List in C#?

The easiest and most common way to add elements to a C# list is to call the Add() function, which adds items to the end of the list. The elements are added to the list in the order you provide them.

var numbers = new List<int>();
numbers.Add(5);
numbers.Add(2);
numbers.Add(9);
numbers.Add(-10);

Console.WriteLine(string.Join(", ", numbers)); // prints '5, 2, 9, -10'

You can also insert an element at a specific position, specified by a zero-based index. For instance, continuing the example above, let’s insert an element at the second position:

var numbers = new List<int>();
numbers.Add(5);
numbers.Add(2);
numbers.Add(9);
numbers.Add(-10);

Console.WriteLine(string.Join(", ", numbers)); // prints '5, 2, 9, -10'

numbers.Insert(1, 42);
Console.WriteLine(string.Join(", ", numbers)); // prints '5, 42, 2, 9, -10'

You can also add several elements at once by using the AddRange() method and supplying an IEnumerable<T> value:

var list1 = new List<string>() { "Monday", "Tuesday", "Wednesday" };
var list2 = new List<string>() { "Thursday", "Friday" };
list1.AddRange(list2);
Console.WriteLine(string.Join(", ", list1)); // prints 'Monday, Tuesday, Wednesday, Thursday, Friday'

How about removing an element? There are several options at your disposal:

  • Remove() – allows you to specify an item and the list will remove the first occurrence of that item, if it exists
  • RemoveAt() – removes the item at the specified index
  • RemoveAll() – removes all of the items that match the specified condition
  • RemoveRange() – removes a specified number of elements starting at a given index position
list1.Remove("Saturday"); // does nothing, since 'Saturday' isn't an element in the list
list1.Remove("Monday"); // removes 'Monday' from the list
list1.RemoveAt(0); // removes the first item on the list, which is now 'Tuesday'
list1.RemoveAll(x => x.StartsWith('F')); // removes all items which match the condition, in this case, only 'Friday'
Console.WriteLine(string.Join(", ", list1)); // prints 'Wednesday, Thursday'

The methods above have different behaviors regarding what they return and how they handle the case of the item not being found. Remove() has a return type of bool, which means it returns false when the item can’t be found. RemoveAt() has a void return type; if the specified index doesn’t exist, it throws an exception—an ArgumentOutofRangeException, to be exact. RemoveAll() has an int return type and returns the number of elements that were removed.

To wrap-up, you also have the method Clear(), which removes all of the items on the list at once.

Accessing Elements on a List

You can access elements on a list in a number of ways. First, you can access any element by their index, which starts at zero. Be aware, though, that providing an invalid index results in an exception; which is why you should always check before accessing a list when you don’t know the size.

Unlike some other programming languages—such as Python—C# list does not support supplying a negative index in order to access a list in backward order (that is, -1 for the last element, -2 for the second to last, and so on.) Since C# 8, you can access the last item on a list using ^1. See the following examples.

var groceries = new List<string> { "eggs", "milk", "tomatoes", "sugar" };
Console.WriteLine($"First item on the list: {groceries[0]}"); // displays 'eggs'

// Positive indexes (0-based)
Console.WriteLine(groceries[1]); // displays 'milk'
Console.WriteLine(groceries[3]); // displays 'sugar'

// Using ^ operator (from the end)
Console.WriteLine(groceries[^1]); // displays 'sugar' (last item)
Console.WriteLine(groceries[^2]); // displays 'tomatoes' (second-to-last)
Console.WriteLine(groceries[^4]); // displays 'eggs' (fourth-from-last, which is first)

// These will throw ArgumentOutOfRangeException:
Console.WriteLine(groceries[4]);  // Error: index 4 is beyond the end (size is 4)
Console.WriteLine(groceries[-1]); // Error: negative indexes not allowed
Console.WriteLine(groceries[^5]); // Error: ^5 is beyond the start (size is 4)
Console.WriteLine(groceries[^0]); // Error: ^0 is not valid

Knowing the length of the list—which you can obtain using the Count property—you can easily iterate through the list:

var groceries = new List<string> { "eggs", "milk", "tomatoes", "sugar" };
var size = groceries.Count;

for (int i = 0; i < size; i++)
{
    var currentItem = groceries[i];
    // does something with the item
}

You can also iterate through the list in a backward way:
var groceries = new List<string> { "eggs", "milk", "tomatoes", "sugar" };
var size = groceries.Count;

for (int i = 1; i <= size; i++)
{
    var currentItem = groceries[^i]; // iterating the list backwards
    // does something with the item
}

Most of the time, though, you don’t need to use a for loop to iterate through a list. Use the foreach loop, since it’s easier to use and results in more concise code:

var groceries = new List<string> { "eggs", "milk", "tomatoes", "sugar" };
var size = groceries.Count;

foreach (string grocerie in groceries)
{
    Console.WriteLine(grocerie);
}

Advanced Operations with C# Lists

Having worked through the most fundamental C# list features, let’s now cover operations that are a bit more advanced.

How Do You Sort a List in C#?

The List<T> class offers the Sort() method, which allows you to sort the elements of a list in place. Let’s see how it works:

var numbers = new List<int> { 8, 7, 1, 0, 9, 20, -5 };
var words = new List<string> { "wings", "apple", "books", "shield" };
Console.WriteLine(string.Join(", ", numbers)); // displays '8, 7, 1, 0, 9, 20, -5'
Console.WriteLine(string.Join(", ", words)); // displays 'wings, apple, books, shield'

numbers.Sort();
words.Sort();
Console.WriteLine(string.Join(", ", numbers)); // displays '-5, 0, 1, 7, 8, 9, 20'
Console.WriteLine(string.Join(", ", words)); // displays 'apple, books, shield, wings'

So, from the examples above, it looks like Sort() works in an intuitive way, sorting the strings alphabetically and the numbers in ascending order. But what about a custom type? For instance, suppose we have a class like the following:

public class Order
{
    public int Id { get; set; }
    public string Description { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public DateOnly Date { get; set; }
}

Now, let’s create a list and try to sort it:

var orders = new List<Order>
{
   new Order
   {
       Id = 1,
       Description = "Some description",
       Price = 149.99m,
       Date = new DateOnly(2024, 3, 15)
   },
   new Order
   {
       Id = 2,
       Description = "Some description",
       Price = 299.99m,
       Date = new DateOnly(2024, 1, 30)
   },
   new Order
   {
       Id = 3,
       Description = "Yet another description",
       Price = 99.99m,
       Date = new DateOnly(2024, 2, 10)
   }
};

orders.Sort();

If you try to run this, you’ll get a lovely exception:

As you can see, we’ve got a System.InvalidOperationException with the error “Failed to compare two elements in the array.” The exception has an inner exception with the message “At least one object must implement IComparable.”

What does it all mean? To make a long story short, we need to “teach” C# how to compare our objects; otherwise, how is it going to know? 

In order to teach C# how to compare objects, let’s change our class so it implements the IComparable<T> interface:

public class Order : IComparable<Order>
{
    public int Id { get; set; }
    public string Description { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public DateOnly Date { get; set; }

    public int CompareTo(Order? other}
    {
        if (other is null) return 1;
        if (ReferenceEquals(this, other)) return 0;
        return this.Date.CompareTo(other.Date);
    }
}

The IComparable<T> interface defines a single method, CompareTo(), which gets an instance of the same type. It must then return:

  • 0, if the instances are equal
  • 1, if the current instance comes before the other in a comparison
  • -1, if the other instance comes before the current in a comparison

In our case, we are comparing using the Date property. So, let’s test this. First, I’ll add a ToString() override to the order class:

public override string ToString()
{
    return $"Order {Id}: {Description} (${Price:F2}) on {Date:MM/dd/yyyy}";
}

Now, let’s sort and print:

orders.Sort();
foreach (var order in orders)
{
    Console.WriteLine(order.ToString());
}

(The ToString() in the example above isn’t strictly necessary, since Console.Writeline already calls ToString on any parameter it gets, but I’ve added it for easier understanding.)

And now we get a nice result:

Order 2: Some description ($299,99) on 01/30/2024
Order 3: Yet another description ($99,99) on 02/10/2024
Order 1: Some description ($149,99) on 03/15/2024

That works, but what if we wanted to sort by a different property?

As it turns out, the Sort() method has a few different overloads. One of them allows you to provide a custom comparison using a delegate. Let’s use a lambda expression to be more concise:

orders.Sort((a, b) => a.Price.CompareTo(b.Price));
foreach (var order in orders)
{
    Console.WriteLine(order);
}

And here’s what the result looks like:

Order 3: Yet another description ($99,99) on 02/10/2024
Order 1: Some description ($149,99) on 03/15/2024
Order 2: Some description ($299,99) on 01/30/2024

Finally, there’s yet another way to sort a list, obtaining a new list in the process, instead of modifying the original one in place. This approach relies on LINQ:

var ordersSortedByPrice = orders.OrderBy(x => x.Price).ToList();

This approach is very flexible, allowing you to chain several ordering clauses, including ones that order in descending order:

orders = orders
    .OrderBy(o => o.Id)
    .ThenBy(o => o.Description)
    .ThenByDescending(o => o.Price)
    .ToList();

How Do You Reverse a List?

Reversing a list in C# is quite easy. The first way is simply calling the Reverse() method, which reverses the list in place:

List<int> someNumbers = [1, 2, 3, 4];

someNumbers.Reverse();
foreach (var number in someNumbers)
{
    Console.WriteLine(number);
}

The code above prints:

4
3
2
1

You can also use an overload to reverse the items belonging to a given range. For instance, let’s now reverse only the two latest items:

someNumbers.Reverse(2, 2);

The code above means that, starting from the index 2 (the third item) we want to reverse 2 items. So, we’re going to swap 2 and 1, and this is the resulting sequence:

4
3
1
2

Finally, there’s also a LINQ Reverse() method, that returns a new IEnumerable<T> result, which can then be converted to a new list:

IEnumerable<int> someNumbers = new List<int> { 1, 2, 3, 4 };

someNumbers = someNumbers.Reverse().ToList();

How Can You Check If a List Contains a Specific Element in C#?

The easiest way to test for the presence of an item on a list is to use the Contains() method, which returns true if the element is found, and false otherwise.

Another possibility is to use the method IndexOf(). It searches for the specified element and returns the index of the first occurrence, if found. If the element isn’t found, the method returns -1.

Best Practices for Working with C# Lists

To understand best practices when working with lists, let’s recall the main properties of the C# list.

  • It is mutable, and you can add and remove items
  • It allows multiple occurrences of the same value
  • It has several methods that change the list in place

With all that in mind, here are some best practices you should be aware of:

  • Don’t pick a list when there’s a better collection available. For instance, use a HashSet<T> if you need to prevent duplicates. Use a Stack<T> in case you need LIFO capabilities
  • When iterating through a list, prefer a foreach loop in most situations, since doing so is more readable and concise
  • If you need to change the list (for instance, removing elements) prefer a reverse for loop
  • Modifications aren’t allowed in a foreach loop, and if you remove items from the list in a regular for loop, you’ll cause problems, since the indexes would change in the process
  • When passing a list to a method that’s not supposed to modify them, convert them to an immutable collection type

What Are Performance Implications of Using a List in C#?

Before wrapping up, let’s cover some of the performance implications of using lists in C#.

For starters, it’s important to understand how the list works and how it’s possible to add more items to it. Interestingly enough, the List<T> class works by having an internal array. When you create an empty list, the array has an initial capacity (which is not officially documented anywhere, as far as I know, but many sources seem to agree it’s 4.)

When adding items to the list and the array becomes full, the list object creates a new one with double the capacity and copies over the items to the new array. Can you see how this is bad? The copying process itself is already somewhat taxing, but the main problem is that the original array continues lingering there, in memory, adding pressure to the garbage collector.

The solution here is simple. If you know in advance or at least have an educated guess of how big your list will be, provide that number as an argument when you create the list:

var myList = new List<int>(50);

That way, the list will be initialized already with that capacity, and the copying process won’t be necessary as the list grows.

Here are a few more performance considerations:

  • Adding elements to the end of the list is usually efficient, but methods like RemoveAt(), Remove(), and Insert() are expensive, since they require changing the positions of elements in the internal array. Use them with caution
  • Use AddRange rather than multiple Add calls when you need to add a lot of items at once, since AddRange is optimized for such a scenario
  • Use Clear() instead of creating a new list if you’ll need the current capacity of the list again, since Clear() doesn’t reset the capacity to the original value. Also, creating a new list creates another object in memory, which can add more pressure to the garbage collector

Finally, beware of scenarios in which people use many ToList() calls unnecessarily. For instance, in a method like the following:

public IEnumerable<Order> GetActiveOrdersByCustomer(int  customerId)
{
    return _context.Orders
      .Where(x => x.Active)
      .ToList()
      .Where(x => x.CustomerId == customerId)
      .ToList();
}

The first ToList() is completely useless, because it materializes the query before it’s necessary, fetching potentially a large number of rows for further filtering in memory. Besides that, the ToList() call creates an intermediate list object, which isn’t necessary and puts more pressure into the garbage collector.

Another great practice you can adopt when it comes to optimizing performance is to use an application monitoring tool. Stackify Retrace is a solution that helps you monitor the performance of your C# applications, including but not limited to tracking slow processing times, inefficient memory use, and problems with SQL queries—such as the one generated in the above method. To see how Stackify can improve the performance of your C# applications, start your free trial today.

Conclusion

The C# list class is a powerful and flexible way of working with collections in C#. It makes use of generics to create a type-safe environment for you to handle items. You can easily add, insert, and remove items, as well as sort and manipulate the collection in interesting and useful ways.

The C# list class isn’t the perfect solution for everything, though. There are scenarios in which you’re better off with a different type altogether. As you’ve learned in the post, there are performance issues you might run into if you’re not careful with certain operations.

I hope the article was helpful. Happy coding and thanks for reading.

Read the whole story
alvinashcraft
1 hour ago
reply
West Grove, PA
Share this story
Delete

Getting ready for Tailwind v4.0

1 Share

The word “tailwind” literally means the wind blowing in the same direction as a plane or boat’s course of movement. It helps an object travel faster and reach its destination quicker, ensuring speed and efficiency.

Getting Ready For Tailwind V4.0

Tailwind CSS is a utility-first framework that lets you “rapidly build modern websites without leaving your HTML.” It’s not every developer’s cup of tea, but Tailwind CSS has gained significant popularity since its release in 2019.

Today, you’ll likely find Tailwind CSS listed alongside established names like Bootstrap and Bulma when you search for “Top [insert number] CSS frameworks.”

This article will provide a preview and in-depth analysis of the next version, Tailwind v4.0. We’ll cover strategies for migrating existing projects and examples demonstrating the new features of Tailwind v4.0. We’ll also compare it with similar CSS frameworks, and explore the benefits and limitations of using this framework.

Getting started with Tailwind v4.0

Tailwind v4.0 has been in development for several months, and the first public beta version was released in November 2024.

For more detailed information, you can visit the prerelease documentation, but this guide will highlight some of the many new and exciting features developers can look forward to in Tailwind CSS v4.0

New performance engine

The Tailwind team announced a new performance engine, Tailwind Oxide, in March 2024. Some benefits include a unified toolchain and simplified configuration to speed up the build process.

CSS-first configuration

With the current Tailwind version, the tailwind.config.js file allows you to override Tailwind’s default design tokens. It’s a customization hub where you can add custom utility classes and themes, disable plugins, and more.

Its most important function is defining the content sources for your project so Tailwind can scan for relevant utility class names and produce the right output.

Here’s the default code for the tailwind.config.js file when setting up a new project with Tailwind v3:

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Removed directives

After setting up the config file, the next step involved adding the Tailwind directives to the index.css file.

These are the directives in Tailwind v3:

@tailwind base;
@tailwind components;
@tailwind utilities;

In Tailwind v4, you don’t need a tailwind.config.js file and @tailwind directives. You’ll only need to import "tailwindcss" into your main CSS file, and you’re good to go:

@import "tailwindcss";

This reduces the number of steps when setting up a new project and the number of files.

You can still use a JS config file, for example, if you already have an existing v3 project, by using the new @config directive to load it in your CSS file:

@import "tailwindcss";

@config "../../tailwind.config.js";

However, not every feature, like corePlugins, important, and separator, is likely to be supported in the full v4.0 release. Some options, like safelist may return with changes in behavior.

Source detection

If there are files you don’t want to include, you can use the source() function when importing Tailwind to limit automatic detection:

@import "tailwindcss" source("../src");

For additional sources that Tailwind doesn’t detect by default, like anything in your .gitignore file, you can add them using the @source directive:

@import "tailwindcss";
@source "../node_modules/@my-company/ui-lib/src/components";

You can also disable source detection entirely:

@import "tailwindcss" source(none);

Disabling preflight

You can import the specific individual layers you need for your project and disable Tailwind’s base styles:

@layer theme, base, components, utilities;
@import "tailwindcss/theme" layer(theme);
@import "tailwindcss/utilities" layer(utilities);

Customizing themes

The new CSS-first approach makes adding custom styling to your Tailwind project easier. Any customization will be added directly to the main CSS file instead of a JavaScript configuration file.

If, for instance, you want to configure new colors for a custom theme in Tailwind CSS v3, you’ll need to define the new utility classes in the theme section of the tailwind.config.js file.

Here’s how you’d do it in the JavaScript configuration file:

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {
      colors: {
        background:'#764abc',
        lilac: '#eabad2',
        light: '#eae3f5'
      }
    },
  },
  plugins: [],
}

Here’s how you would add the classes to your HTML file:

<div className="bg-background">
    <header className="flex justify-between py-4 px-8">
      <a href="/" className="text-light">LogRocket - Oscar</a>

      <ul className="text-lilac">
        <li><a href="#">Home</a></li>
        <li><a href="#">About</a></li>
        <li><a href="#">Contact</a></li>
      </ul>
    </header>

In this example, the utility classes are bg-background, text-light, and text-lilac.

In Tailwind CSS v4.0, you configure all your customizations in CSS with the new @theme directive:

@import "tailwindcss";

@theme {
  --color-background-100: #764abc;
  --color-lilac-100: #eabad2;
  --color-light-100: #eae3f5;
}

The utility classes are then added to the HTML. You can choose to have different shades of the same color like the default Tailwind colors:

<div className="bg-background-100">
    <header className="flex justify-between py-4 px-8">
      <a href="/" className="text-light-100">LogRocket - Oscar</a>

      <ul className="text-lilac-100">
        <li><a href="#">Home</a></li>
        <li><a href="#">About</a></li>
        <li><a href="#">Contact</a></li>
      </ul>
    </header>

If you’re testing it out with VS Code, the @import directive may be highlighted as an error but don’t worry, it’ll work just fine.

Note that the examples above were created with Tailwind CSS and React, hence why we have className in the HTML and not class. The utilities remain the same no matter the framework you’re working with.

Theme variables

From the previous example, you can see that CSS variables drive all theme styling in Tailwind v4.0:

@theme {
  --font-display: "Poppins", "sans-serif";

  --ease-fluid: cubic-bezier(0.3,0,0,1);

  --color-background-100: #764abc;
}

In v4.0, you can override a specific theme namespace — that is, the default utilities for colors, fonts, text, and more, or the entire Tailwind theme and configure your own. You can easily configure custom styling for essentially every Tailwind utility in the main CSS file:

List Of Tailwind Utilities

To override the entire default theme, use --*: initial. If you wanted to override the default Tailwind font and define your own, you’d use --font-*: initial followed by your custom styling:

@import "tailwindcss";

@theme {
  --font-*: initial
  --font-display: "Poppins", "sans-serif";
}

In this case, font-display will be the only font-family utility available in your project.

You can set default styling for a custom property using double-dashes. Here’s a page with the default Tailwind CSS font and text styling:

Default Tailwind CSS Font And Text Styling

Here’s the HTML markup for this page:

<div className="bg-background h-screen">
    <header className="flex justify-between py-4 px-8">
      <a href="/" className="text-lg text-light font-bold">LogRocket - Oscar</a>

      <ul className="hidden md:flex flex- items-center align-middle gap-4 font-bold text-lilac">
        <li>
          <a href="#" className="py-2 px-4 rounded-md">Home</a>
        </li>
        <li><a href="#" className="">About</a></li>
        <li><a href="#" className="">Contact</a></li>
      </ul>
    </header>
    <div className="container px-32 py-32">
      <div className="flex">
        <div>
          <h1 className="text-5xl text-lilac font-bold">Tailwind CSS</h1>
          <br />
          <h3 className="text-3xl text-light font-semibold">
            Build websites with utility classes from the comfort of your             HTML
          </h3>
          <br />
          <p className="text-2xl text-light">
              Lorem ipsum dolor sit amet consectetur adipisicing elit. Fu               gi at veniet atque unde laudantium. Ipsa nam quisquam quod               non fficiis porro? Lorem ipsum dolor, sit amet consectetur               adipisicing elit. Eos iure nemo a hic sunt incidunt?
          </p>
        </div>
      </div>
    </div>
  </div>

We’re using the custom colors from the earlier example, and configuring new font and text styling:

@import "tailwindcss";
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');

@theme {  
  --font-display : "Poppins", sans-serif;  
  --font-logo: "Roboto", sans-serif;

  --text-logo: 1.5rem;
  --text-logo--font-weight: 700;

  --text-big: 6rem;
  --text-big--font-weight: 700;
  --text-big--letter-spacing: -0.025em;

  --color-background-100: #764abc;
  --color-lilac-100: #eabad2;
  --color-light-100: #eae3f5;
}

In this example, we’re importing two fonts and saving them under the --font-display and --font-logo variables, to be used for the logo and h1 header. We’re also configuring new text sizes and default styling for both.

So, when you add the utility class text-logo in your HTML, the element will have a font size of 1.5rem and font-weight of 700 by default. Similarly, any element with the class name, text-big, will have a font-size of 6rem, font-weight of 700, and letter-spacing of -0.025em by default.

Now we add the new utility classes into the HTML file:

 <div className="bg-background-100  h-screen">
    <header className="flex justify-between py-4 px-8">
      <a href="/" className="font-logo text-logo text-light-100">LogRocket - Oscar</a>

      <ul className="hidden md:flex flex items-center align-middle gap-4 font-display text-lilac-100">
        <li>
          <a href="#" className="py-2 px-4 rounded-md">Home</a>
        </li>
        <li><a href="#" className="">About</a></li>
        <li><a href="#" className="">Contact</a></li>
      </ul>
    </header>
    <div className="container px-32 py-32 font-display">
      <div className="flex">
        <div>
          <h1 className="text-lilac-100 text-big">Tailwind CSS</h1>
          <br />
          <h3 className="text-3xl text-light-100">
            Build websites with utility classes from the comfort of your             HTML
          </h3>
          <br />
          <p className="text-2xl text-light">
              Lorem ipsum dolor sit amet consectetur adipisicing elit. Fu               gi at veniet atque unde laudantium. Ipsa nam quisquam quod               non fficiis porro? Lorem ipsum dolor, sit amet consectetur               adipisicing elit. Eos iure nemo a hic sunt incidunt?
          </p>
        </div>
      </div>
    </div>
  </div>

Here’s a screenshot of the page with the custom styling:

Custom Styling Sample

In Tailwind v4.0, there will be less dependency on the default Tailwind values as multiple classes can be replaced with one custom utility. In our example, the text-big class name replaces the text-5xl and text-bold utility classes for the h1 header.

Again, this isn’t limited to specific namespaces — you can do this with every utility.

Simplified theme configuration

Some utilities are no longer based on your theme configuration in Tailwind v4.0. You’ll be able to specify exactly what you want directly in your HTML file without extra configuration.

In Tailwind v3, you’d need to define the number of columns in your tailwind.config.js file, but in Tailwind v4.0 you can use any number from as small as grid-cols-5 to as large as grid-cols-73. It also applies to the z-index utilities (for example, z-40) and opacity-*.

Tailwind v4.0 also has built-in support for variants like data-*. You can use them without arbitrary values.

The main benefit of these changes is that developers will be able to spend less time configuring non-essential, or non-core, design tokens.

Dynamic spacing scale

Spacing utilities, like m-*, w-*, mt-*, px-*, and more, are generated dynamically using a base spacing value of 0.25rem defined in the default Tailwind v4.0 theme.

Every multiple of the base spacing value is available in the spacing scale. So if mt-1 is 0.25rem, mt-2 will be 0.25rem multiplied by two, which is 0.5rem, and mt-21 will be 5.25rem:

Tailwind V4 Spacing Scale

You can use spacing utilities with values that aren’t explicitly defined. In Tailwind v3, you’d need to use an arbitrary value like mt-[5.25rem] or a custom theme. There’s no need for additional configuration and you can create more consistent designs.

If you want to limit the available spacing values, you can disable the default variable and define a custom scale:

@theme {
  --spacing: initial
  --spacing-1: 0.25rem
  --spacing-2: 0.5rem
  --spacing-4: 1rem
  --spacing-8: 2rem
  --spacing-12: 3rem
}

With this setup, every Tailwind spacing utility will only use the specifically defined values.

Updated color palette

Tailwind v4 is moving from the default rgb color palette to oklch, which enables more vibrant colors, and is less limited than rgb:

Tailwind Oklch Color Palette

Container query support

Container queries now have built-in support in Tailwind CSS v4.0; you won’t need the @tailwindcss/container-queries plugin to create responsive containers.

Container queries are used to apply styling to an element based on the size of its parent container. This means your site’s layout adapts to individual components rather than the entire viewport.

In v4.0, you create container queries by adding the @container utility to a parent element. For the child elements, you use responsive utilities like @sm and @lg to apply styling based on the parent’s size:

<div className="@container">
   <header className="flex justify-between @sm:grid-cols-2 @lg:grid-cols-4">
     <!-- child content -->
   </header>
</div>

Tailwind v4.0 also introduces a new @max-* variant for max-width container queries. It makes it easier to add styling when the container goes below a certain size. You can combine @min-* and @max-* to define container query ranges:

<div className="@container">
   <div className="flex @min-md:@max-xl:hidden">
     <!-- child content -->
   </div>
</div>

In this code, the child div will be hidden when the width of the parent container is between md and xl (768px and 1280px).

Use cases for container queries include navigation, sidebars, cards, image galleries, and responsive text. They also provide more flexibility and are well-supported across browsers, so you can start using them in your v4.0 projects.

Migrating from v3 to Tailwind CSS v4.0

If you want to upgrade a v3 project to v4, Tailwind has provided an upgrade tool to do most of the work for you.

To upgrade your project, run the following command:

npx @tailwindcss/upgrade@next

The upgrade tool will automate several tasks like updating dependencies, migrating your JS config file to CSS, and handling changes in your template files.

Tailwind recommends using a new branch for the upgrade, to keep your main branch intact, and carefully reviewing the diff. Running a git diff command helps you see and understand the changes in your project. You’d also want to test your project in a browser to confirm everything is working as it should.

Complex projects might require you to make manual adjustments, and Tailwind has outlined key changes and how to adapt to them, which we’ll cover below.

Dependency changes

PostCSS plugin: In v4.0, the PostCSS plugin is now available as a dedicated package, @tailwindcss/postcss. You can remove postcss-import and auto-prefixer from the postcss.config.mjs file in your existing project:

export default {
  plugins: {
    '@tailwindcss/postcss': {},
  },
};

If you are starting a new project, you can now install Tailwind alongside the PostCSS plugin by running the following command:

npm install tailwindcss@next @tailwindcss/postcss@next

Vite plugin: Tailwind CSS v4.0 also has a new dedicated Vite plugin, which they recommend you migrate to from the PostCSS plugin:

import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [
    tailwindcss()
  ],
});

As we’ve seen with PostCSS, you can install v4.0 along with the Vite plugin when setting up a new project:

npm install tailwindcss@next @tailwindcss/vite@next 

Tailwind CLI: Using the CLI tool is the easiest and fastest way to set up Tailwind CSS, and it now resides in a dedicated @tailwind/cli package.

You’d need to update your build commands accordingly:

npx @tailwindcss/cli -i input.css -o output.css

Deprecated utilities

Several outdated or undocumented utilities have been removed and replaced with modern alternatives:

Deprecated Tailwind Utilities

Configuring the container utility

In v4.0, you configure the container utility with @utility:

@import "tailwindcss";

@utility container {
  margin-inline: auto;
  padding-inline: 2rem;
}

Configuration options like center and padding don’t exist in v4.0.

Default scale changes

Default scale adjustments have been made to every shadow, blur, and border-radius utility, to make sure they have a named value:

Default Scale Changes In Tailwind V4

You’d need to replace each utility in your project to ensure things don’t look different.

Default border color change

In v3, the default border color is gray-200. You didn’t need to explicitly set a color when using the border utility:

<header className="flex justify-between border-b-2 py-4 px-8">
      <--! content --> 
  </header>

Default Border Color In Tailwind V3

In Tailwind CSS v4, the border color is updated to currentColor, and your current project may experience a visual change if you don’t specify a color anywhere you use the border utility.

Here’s the default border color in v4.0:

Default Border Color In Tailwind V4

To maintain the v3 default behavior, you can add these CSS lines to your project:

the v3 behavior:
@import "tailwindcss";

@layer base {
  *,
  ::after,
  ::before,
  ::backdrop,
  ::file-selector-button {
    border-color: var(--color-gray-200, currentColor);
  }
}

Default ring width change

The ring utility adds a 3px ring in v3, but it defaults to 1px in v4. Replace any usage of the ring utility with ring-3 when updating your project to maintain its appearance.

Default placeholder change

In v4, placeholder text will use the current text color at 50% opacity by default. It uses the gray-400 color in v3, and if you want to preserve this behavior, add this to your CSS:

@layer base {
  input::placeholder,
  textarea::placeholder {
    color: theme(--color-gray-400);
  }
}

Outline changes

Also in v4, the outline-none utility doesn’t add a transparent 2px outline like it does in v3. There’s a new outline-hidden utility in v4 that behaves like outline-none from v3.

When upgrading your project, you’d need to replace outline-none with outline-hidden to maintain its current state, except you want to remove the outline entirely.

Adding custom utilities

Custom utilities now work with the new @utility API instead of @layer utility. This change ensures compatibility with native cascade layers.

tThey are now just single-class names and no longer complex selectors:

@utility tab-4 {
  tab-size: 4;
}

Stacking order-sensitive variants

Tailwind v4.0 stacks variants like first and last from left to right, so you will need to order the variants in your project.

CSS variables in arbitrary values

The syntax for variables in arbitrary values has changed from square brackets to parenthesis to avoid ambiguity with new CSS standards. You’d need to update this in your project:

<div class="bg-(--brand-color)">
  <!-- ... -->
</div>

Hover styles on mobile

In v4, hover styles will only work on devices that support hover interactions to align with accessibility practices.

You can enable backward compatibility by defining a hover variant in the CSS file:

@import "tailwindcss";

@variant hover (&:hover);

Using the theme() function

Tailwind CSS v4.0 generates variables for all theme values so the theme() function is not necessary. Tailwind recommends that all theme() functions in your project be replaced with CSS variables wherever possible:

@import "tailwindcss";

.my-class {
  background-color: var(--color-red-500);
}

For more details about the changes coming in Tailwind v4.0, you should visit the prerelease documentation.

Tailwind and alternative CSS frameworks

The most obvious alternative to Tailwind CSS is Bootstrap, the most popular CSS framework in the world. It has an extensive library of predefined components.

Bootstrap is perhaps more beginner-friendly than Tailwind CSS. You can create ready-to-use components using specific and straightforward class names. Tailwind requires you to understand the utilities and their underlying CSS rules.

Another advantage Bootstrap has over Tailwind CSS is that it includes JavaScript by default, so you can do more backend stuff. Tailwind CSS has to be combined with JS frameworks.

However, Bootstrap is not as customizable or as flexible as Tailwind CSS. A long-standing argument is that all Bootstrap sites look the same. With its utility-first approach, Tailwind offers more flexibility and control.

Tailwind CSS cons

More utility-first CSS frameworks have popped up in recent years, like missing.css and Mojo CSS. None have been able to take the crown from Tailwind, but that’s not to say it’s not without its fair share of limitations:

Steep learning curve: As earlier mentioned, the utility-first approach and large number of classes can be difficult for beginners to learn.

Code readability: Because you’re working mainly in your HTML file, the code can become hard to read as each element accumulates utilities.

Inconsistent design: The flexibility of Tailwind CSS can lead to inconsistent designs across a project if you’re not mindful.

Switching frameworks: Projects can become tightly coupled with Tailwind CSS, making it difficult to switch to another framework.

Transitioning to Tailwind CSS v4.0

Upgrading your existing projects to the new version of Tailwind may seem like a difficult task, and this is true if you have a complex project, but the benefits are worthwhile. Tailwind is making everything faster and simpler by removing additional tools and files and providing clearer syntax.

The post Getting ready for Tailwind v4.0 appeared first on LogRocket Blog.

Read the whole story
alvinashcraft
1 hour ago
reply
West Grove, PA
Share this story
Delete
Next Page of Stories