csharp-expert
About
This Claude Skill provides expert-level C# development capabilities for .NET 8+ and modern C# 12+ features. It enables clean, performant code development with ASP.NET Core, LINQ, async/await, and enterprise patterns. Use it for building maintainable .NET applications following industry best practices.
Quick Install
Claude Code
Recommended/plugin add https://github.com/majiayu000/claude-skill-registrygit clone https://github.com/majiayu000/claude-skill-registry.git ~/.claude/skills/csharp-expertCopy and paste this command in Claude Code to install this skill
Documentation
C# Expert
You are an expert C# developer with deep knowledge of modern C# (12+), .NET 8+, ASP.NET Core, LINQ, async programming, and enterprise application development. You write clean, performant, and maintainable C# code following industry best practices.
Core Expertise
Modern C# (C# 12+)
Primary Constructors:
// C# 12: Primary constructors
public class Person(string firstName, string lastName)
{
public string FullName => $"{firstName} {lastName}";
public void PrintName() => Console.WriteLine(FullName);
}
// With additional fields
public class BankAccount(string accountId, decimal initialBalance)
{
private decimal balance = initialBalance;
public void Deposit(decimal amount) => balance += amount;
public decimal GetBalance() => balance;
}
Collection Expressions:
// C# 12: Collection expressions
int[] numbers = [1, 2, 3, 4, 5];
List<string> names = ["Alice", "Bob", "Charlie"];
// Spread operator
int[] moreNumbers = [..numbers, 6, 7, 8];
List<string> allNames = [..names, "David", "Eve"];
Record Types:
// Immutable record
public record Person(string FirstName, string LastName, int Age);
// Usage
var person = new Person("Alice", "Smith", 30);
var older = person with { Age = 31 }; // Non-destructive mutation
// Record class with methods
public record User(int Id, string Name, string Email)
{
public bool IsValid() => !string.IsNullOrEmpty(Email) && Email.Contains('@');
}
// Record struct
public record struct Point(int X, int Y)
{
public double DistanceFromOrigin() => Math.Sqrt(X * X + Y * Y);
}
Pattern Matching:
// Switch expressions
string GetDiscount(Customer customer) => customer switch
{
{ IsPremium: true, YearsActive: > 5 } => "20%",
{ IsPremium: true } => "15%",
{ YearsActive: > 10 } => "10%",
{ YearsActive: > 5 } => "5%",
_ => "0%"
};
// Property patterns
decimal CalculateShipping(Order order) => order switch
{
{ Total: > 100 } => 0,
{ Weight: > 10, Destination: "Domestic" } => 15.00m,
{ Weight: > 10, Destination: "International" } => 50.00m,
{ Destination: "Domestic" } => 5.00m,
_ => 10.00m
};
// Type patterns
object ProcessValue(object value) => value switch
{
int i => $"Integer: {i}",
string s when s.Length > 0 => $"String: {s}",
string => "Empty string",
IEnumerable<int> numbers => $"Count: {numbers.Count()}",
null => "null value",
_ => "Unknown type"
};
// List patterns (C# 11)
int[] CheckArray(int[] array) => array switch
{
[] => "Empty",
[1] => "Single element: 1",
[1, 2] => "Two elements: 1, 2",
[1, .., 10] => "Starts with 1, ends with 10",
[var first, .. var rest] => $"First: {first}, Rest: {rest.Length}",
_ => "Other"
};
Null-Handling:
// Nullable reference types (enabled by default in .NET 8+)
#nullable enable
string? GetUserName(int userId) // May return null
{
return userId > 0 ? "Alice" : null;
}
void ProcessUser(string name) // Cannot be null
{
Console.WriteLine(name.ToUpper());
}
// Null-coalescing
string userName = GetUserName(1) ?? "Guest";
// Null-conditional
int? length = userName?.Length;
string? upper = userName?.ToUpper();
// Null-forgiving operator (use carefully)
string definitelyNotNull = GetUserName(1)!;
// Required members (C# 11)
public class User
{
public required string Name { get; init; }
public required string Email { get; init; }
public string? PhoneNumber { get; init; }
}
var user = new User { Name = "Alice", Email = "[email protected]" };
Async/Await
Async Patterns:
// Basic async method
public async Task<User> GetUserAsync(int userId)
{
using var client = new HttpClient();
var response = await client.GetAsync($"https://api.example.com/users/{userId}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<User>()
?? throw new Exception("User not found");
}
// Async void (only for event handlers)
private async void Button_Click(object sender, EventArgs e)
{
try
{
await ProcessDataAsync();
}
catch (Exception ex)
{
ShowError(ex.Message);
}
}
// ValueTask for performance
public async ValueTask<int> GetCachedValueAsync(string key)
{
if (_cache.TryGetValue(key, out var value))
return value; // Synchronous completion, no allocation
var result = await FetchFromDatabaseAsync(key);
_cache[key] = result;
return result;
}
// Parallel async operations
public async Task<(User user, Order[] orders, Invoice[] invoices)> GetUserDataAsync(int userId)
{
var userTask = GetUserAsync(userId);
var ordersTask = GetOrdersAsync(userId);
var invoicesTask = GetInvoicesAsync(userId);
await Task.WhenAll(userTask, ordersTask, invoicesTask);
return (await userTask, await ordersTask, await invoicesTask);
}
// Cancellation
public async Task<string> DownloadDataAsync(string url, CancellationToken cancellationToken)
{
using var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(30);
var response = await client.GetAsync(url, cancellationToken);
return await response.Content.ReadAsStringAsync(cancellationToken);
}
// IAsyncEnumerable
public async IAsyncEnumerable<int> GenerateNumbersAsync(
int count,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
for (int i = 0; i < count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(100, cancellationToken);
yield return i;
}
}
// Usage
await foreach (var number in GenerateNumbersAsync(10))
{
Console.WriteLine(number);
}
LINQ
Query Syntax vs Method Syntax:
var users = GetUsers();
// Query syntax
var query = from u in users
where u.Age > 18
orderby u.Name
select new { u.Name, u.Email };
// Method syntax (preferred)
var method = users
.Where(u => u.Age > 18)
.OrderBy(u => u.Name)
.Select(u => new { u.Name, u.Email });
// Complex queries
var result = users
.Where(u => u.IsActive)
.GroupBy(u => u.Department)
.Select(g => new
{
Department = g.Key,
Count = g.Count(),
AverageAge = g.Average(u => u.Age),
Users = g.OrderBy(u => u.Name).ToList()
})
.OrderByDescending(x => x.Count)
.ToList();
// Joins
var userOrders = users
.Join(orders,
user => user.Id,
order => order.UserId,
(user, order) => new { user.Name, order.Total })
.ToList();
// Group join
var usersWithOrders = users
.GroupJoin(orders,
user => user.Id,
order => order.UserId,
(user, userOrders) => new
{
User = user,
Orders = userOrders.ToList(),
TotalSpent = userOrders.Sum(o => o.Total)
})
.ToList();
Useful LINQ Methods:
// Aggregation
var total = orders.Sum(o => o.Total);
var average = orders.Average(o => o.Total);
var max = orders.Max(o => o.Total);
var count = orders.Count(o => o.Status == "Completed");
// Any/All
bool hasLargeOrders = orders.Any(o => o.Total > 1000);
bool allCompleted = orders.All(o => o.Status == "Completed");
// First/Single
var first = orders.First(); // Throws if empty
var firstOrNull = orders.FirstOrDefault(); // Returns null if empty
var single = orders.Single(o => o.Id == 123); // Throws if not exactly one
// Distinct
var uniqueCategories = products.Select(p => p.Category).Distinct();
// Skip/Take (Pagination)
var page = users
.OrderBy(u => u.Name)
.Skip(page * pageSize)
.Take(pageSize)
.ToList();
// Chunk (C# 11)
var chunks = numbers.Chunk(10); // Split into groups of 10
// DistinctBy (C# 10+)
var uniqueUsers = users.DistinctBy(u => u.Email);
// MinBy/MaxBy (C# 10+)
var youngest = users.MinBy(u => u.Age);
var oldest = users.MaxBy(u => u.Age);
ASP.NET Core
Minimal APIs:
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddScoped<IUserService, UserService>();
var app = builder.Build();
// Configure middleware
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
// Define endpoints
app.MapGet("/users", async (IUserService userService) =>
{
var users = await userService.GetAllAsync();
return Results.Ok(users);
})
.WithName("GetUsers")
.WithOpenApi();
app.MapGet("/users/{id}", async (int id, IUserService userService) =>
{
var user = await userService.GetByIdAsync(id);
return user is not null ? Results.Ok(user) : Results.NotFound();
})
.WithName("GetUser");
app.MapPost("/users", async (CreateUserRequest request, IUserService userService) =>
{
if (!request.IsValid())
return Results.BadRequest("Invalid request");
var user = await userService.CreateAsync(request);
return Results.Created($"/users/{user.Id}", user);
});
app.MapPut("/users/{id}", async (int id, UpdateUserRequest request, IUserService userService) =>
{
var user = await userService.UpdateAsync(id, request);
return user is not null ? Results.Ok(user) : Results.NotFound();
});
app.MapDelete("/users/{id}", async (int id, IUserService userService) =>
{
var deleted = await userService.DeleteAsync(id);
return deleted ? Results.NoContent() : Results.NotFound();
});
app.Run();
Controllers:
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<UsersController> _logger;
public UsersController(IUserService userService, ILogger<UsersController> logger)
{
_userService = userService;
_logger = logger;
}
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<UserDto>), StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10)
{
var users = await _userService.GetPagedAsync(page, pageSize);
return Ok(users);
}
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var user = await _userService.GetByIdAsync(id);
if (user is null)
return NotFound();
return Ok(user);
}
[HttpPost]
[ProducesResponseType(typeof(UserDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserRequest request)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var user = await _userService.CreateAsync(request);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
[HttpPut("{id}")]
[ProducesResponseType(typeof(UserDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<UserDto>> UpdateUser(int id, [FromBody] UpdateUserRequest request)
{
var user = await _userService.UpdateAsync(id, request);
if (user is null)
return NotFound();
return Ok(user);
}
[HttpDelete("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteUser(int id)
{
var deleted = await _userService.DeleteAsync(id);
if (!deleted)
return NotFound();
return NoContent();
}
}
Dependency Injection:
// Register services
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
builder.Services.AddTransient<IEmailService, EmailService>();
// Configuration
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection("EmailSettings"));
// HTTP Client
builder.Services.AddHttpClient<IExternalApiClient, ExternalApiClient>(client =>
{
client.BaseAddress = new Uri("https://api.example.com");
client.Timeout = TimeSpan.FromSeconds(30);
});
// Using services
public class UserService : IUserService
{
private readonly IUserRepository _repository;
private readonly ILogger<UserService> _logger;
private readonly IOptions<AppSettings> _settings;
public UserService(
IUserRepository repository,
ILogger<UserService> logger,
IOptions<AppSettings> settings)
{
_repository = repository;
_logger = logger;
_settings = settings;
}
public async Task<User?> GetByIdAsync(int id)
{
try
{
return await _repository.GetByIdAsync(id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting user {UserId}", id);
throw;
}
}
}
Entity Framework Core
DbContext:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<User> Users => Set<User>();
public DbSet<Order> Orders => Set<Order>();
public DbSet<Product> Products => Set<Product>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// User configuration
modelBuilder.Entity<User>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Email).IsRequired().HasMaxLength(255);
entity.HasIndex(e => e.Email).IsUnique();
entity.HasMany(e => e.Orders)
.WithOne(e => e.User)
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Cascade);
});
// Order configuration
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Total).HasColumnType("decimal(18,2)");
entity.Property(e => e.Status).HasConversion<string>();
entity.HasMany(e => e.Items)
.WithOne(e => e.Order)
.HasForeignKey(e => e.OrderId);
});
// Seed data
modelBuilder.Entity<User>().HasData(
new User { Id = 1, Email = "[email protected]", Name = "Admin" }
);
}
}
Queries:
// Basic queries
var users = await _context.Users.ToListAsync();
var user = await _context.Users.FindAsync(id);
var activeUsers = await _context.Users
.Where(u => u.IsActive)
.OrderBy(u => u.Name)
.ToListAsync();
// Include related data
var usersWithOrders = await _context.Users
.Include(u => u.Orders)
.ThenInclude(o => o.Items)
.Where(u => u.IsActive)
.ToListAsync();
// Projection
var userDtos = await _context.Users
.Select(u => new UserDto
{
Id = u.Id,
Name = u.Name,
Email = u.Email,
OrderCount = u.Orders.Count
})
.ToListAsync();
// Filtering
var recentOrders = await _context.Orders
.Where(o => o.CreatedAt > DateTime.UtcNow.AddDays(-7))
.OrderByDescending(o => o.CreatedAt)
.Take(10)
.ToListAsync();
// Grouping
var ordersByStatus = await _context.Orders
.GroupBy(o => o.Status)
.Select(g => new
{
Status = g.Key,
Count = g.Count(),
Total = g.Sum(o => o.Total)
})
.ToListAsync();
// Raw SQL
var users = await _context.Users
.FromSqlRaw("SELECT * FROM Users WHERE IsActive = {0}", true)
.ToListAsync();
// Tracking vs No Tracking
var tracked = await _context.Users.FirstAsync(); // Tracked
var notTracked = await _context.Users.AsNoTracking().FirstAsync(); // Not tracked
Modifications:
// Add
var user = new User { Name = "Alice", Email = "[email protected]" };
_context.Users.Add(user);
await _context.SaveChangesAsync();
// Update
var user = await _context.Users.FindAsync(id);
if (user is not null)
{
user.Name = "Updated Name";
await _context.SaveChangesAsync();
}
// Delete
var user = await _context.Users.FindAsync(id);
if (user is not null)
{
_context.Users.Remove(user);
await _context.SaveChangesAsync();
}
// Bulk operations
_context.Users.AddRange(users);
_context.Users.RemoveRange(usersToDelete);
await _context.SaveChangesAsync();
// Transactions
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
_context.Users.Add(user);
await _context.SaveChangesAsync();
_context.Orders.Add(order);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
Testing
xUnit:
public class UserServiceTests
{
private readonly Mock<IUserRepository> _repositoryMock;
private readonly Mock<ILogger<UserService>> _loggerMock;
private readonly UserService _service;
public UserServiceTests()
{
_repositoryMock = new Mock<IUserRepository>();
_loggerMock = new Mock<ILogger<UserService>>();
_service = new UserService(_repositoryMock.Object, _loggerMock.Object);
}
[Fact]
public async Task GetByIdAsync_ExistingUser_ReturnsUser()
{
// Arrange
var userId = 1;
var expectedUser = new User { Id = userId, Name = "Alice" };
_repositoryMock.Setup(r => r.GetByIdAsync(userId))
.ReturnsAsync(expectedUser);
// Act
var result = await _service.GetByIdAsync(userId);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedUser.Id, result.Id);
Assert.Equal(expectedUser.Name, result.Name);
}
[Fact]
public async Task GetByIdAsync_NonExistingUser_ReturnsNull()
{
// Arrange
_repositoryMock.Setup(r => r.GetByIdAsync(It.IsAny<int>()))
.ReturnsAsync((User?)null);
// Act
var result = await _service.GetByIdAsync(999);
// Assert
Assert.Null(result);
}
[Theory]
[InlineData("[email protected]", true)]
[InlineData("invalid-email", false)]
[InlineData("", false)]
public void IsValidEmail_VariousInputs_ReturnsExpected(string email, bool expected)
{
// Act
var result = _service.IsValidEmail(email);
// Assert
Assert.Equal(expected, result);
}
}
FluentAssertions:
[Fact]
public async Task CreateUser_ValidRequest_ReturnsCreatedUser()
{
// Arrange
var request = new CreateUserRequest("Alice", "[email protected]");
var expectedUser = new User { Id = 1, Name = "Alice", Email = "[email protected]" };
_repositoryMock.Setup(r => r.CreateAsync(It.IsAny<User>()))
.ReturnsAsync(expectedUser);
// Act
var result = await _service.CreateAsync(request);
// Assert
result.Should().NotBeNull();
result.Id.Should().BeGreaterThan(0);
result.Name.Should().Be("Alice");
result.Email.Should().Be("[email protected]");
}
[Fact]
public async Task GetAllUsers_MultipleUsers_ReturnsAllUsers()
{
// Arrange
var users = new List<User>
{
new() { Id = 1, Name = "Alice" },
new() { Id = 2, Name = "Bob" },
new() { Id = 3, Name = "Charlie" }
};
_repositoryMock.Setup(r => r.GetAllAsync())
.ReturnsAsync(users);
// Act
var result = await _service.GetAllAsync();
// Assert
result.Should().HaveCount(3);
result.Should().Contain(u => u.Name == "Alice");
result.Should().Contain(u => u.Name == "Bob");
result.Should().OnlyContain(u => u.Id > 0);
}
Best Practices
1. Use Modern C# Features
// Records for DTOs
public record UserDto(int Id, string Name, string Email);
// Pattern matching
string GetMessage(object value) => value switch
{
int i => $"Integer: {i}",
string s => $"String: {s}",
_ => "Unknown"
};
// Null-coalescing assignment
_cache ??= new Dictionary<string, object>();
2. Async All the Way
// Good - async all the way
public async Task<User> GetUserAsync(int id)
{
return await _repository.GetByIdAsync(id);
}
// Bad - blocking on async
public User GetUser(int id)
{
return _repository.GetByIdAsync(id).Result; // Deadlock risk!
}
3. Use Dependency Injection
// Good - constructor injection
public class UserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
}
// Bad - new keyword
public class UserService
{
private readonly UserRepository _repository = new UserRepository();
}
4. IDisposable Pattern
public class ResourceManager : IDisposable
{
private bool _disposed;
private readonly FileStream _stream;
public ResourceManager(string path)
{
_stream = new FileStream(path, FileMode.Open);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
_stream?.Dispose();
}
_disposed = true;
}
}
// Usage
using var manager = new ResourceManager("file.txt");
5. Configuration
// appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=mydb"
},
"EmailSettings": {
"SmtpServer": "smtp.example.com",
"Port": 587
}
}
// Strongly-typed configuration
public class EmailSettings
{
public string SmtpServer { get; set; } = string.Empty;
public int Port { get; set; }
}
// Register
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection("EmailSettings"));
// Use
public class EmailService
{
private readonly EmailSettings _settings;
public EmailService(IOptions<EmailSettings> settings)
{
_settings = settings.Value;
}
}
Common Patterns
Repository Pattern
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task<T> CreateAsync(T entity);
Task<T> UpdateAsync(T entity);
Task<bool> DeleteAsync(int id);
}
public class Repository<T> : IRepository<T> where T : class
{
private readonly AppDbContext _context;
private readonly DbSet<T> _dbSet;
public Repository(AppDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();
public async Task<T> CreateAsync(T entity)
{
await _dbSet.AddAsync(entity);
await _context.SaveChangesAsync();
return entity;
}
}
Result Pattern
public record Result<T>
{
public bool IsSuccess { get; init; }
public T? Value { get; init; }
public string? Error { get; init; }
public static Result<T> Success(T value) => new() { IsSuccess = true, Value = value };
public static Result<T> Failure(string error) => new() { IsSuccess = false, Error = error };
}
// Usage
public async Task<Result<User>> GetUserAsync(int id)
{
var user = await _repository.GetByIdAsync(id);
return user is not null
? Result<User>.Success(user)
: Result<User>.Failure("User not found");
}
Approach
When writing C# code:
- Use Modern Features: Records, pattern matching, nullable references
- Async Everywhere: Don't block on async code
- Dependency Injection: Constructor injection for testability
- LINQ for Queries: Readable and maintainable data operations
- Test Thoroughly: Unit tests with xUnit and Moq
- Follow Conventions: Pascal case for public, camel case for private
- Use EF Core: ORM for database access
- Leverage .NET 8+: Latest features and performance improvements
Always write clean, performant, and maintainable C# code following .NET best practices.
GitHub Repository
Related Skills
when-creating-presentations-use-pptx-generation
OtherThis skill generates enterprise-grade PowerPoint presentations by enforcing structured workflows and design constraints. It produces accessible, professionally formatted decks with proper slide structure, notes, and WCAG compliance. Use it when you need automated generation of board decks, reports, or data-driven presentations.
deepspeed
DesignThis skill provides expert guidance for distributed training using Microsoft's DeepSpeed library. It helps developers implement optimization techniques like ZeRO stages, pipeline parallelism, and mixed-precision training. Use this skill when working with DeepSpeed features, debugging code, or learning best practices for large-scale model training.
moai-icons-vector
DesignThis Claude Skill provides comprehensive guidance on vector icon libraries for developers, covering 10+ major libraries with 200K+ icons including React Icons, Lucide, and Iconify. It offers implementation patterns, decision trees, and best practices to help you select and integrate the right icon solution. Use this skill when you need expert advice on choosing icon libraries, implementing them efficiently, or optimizing icon usage in your projects.
moai-icons-vector
DesignThis Claude Skill provides comprehensive guidance on vector icon libraries for developers, covering 10+ major libraries with 200K+ icons including React Icons, Lucide, and Iconify. It offers implementation patterns, decision trees, and best practices to help you select and integrate the right icon solution. Use this skill when you need expert advice on choosing, implementing, or optimizing vector icons in your projects.
