frontend-razor

Apply when working with Razor views, MVC layouts, partial views, and tag helpers

16 stars

Best use case

frontend-razor is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Apply when working with Razor views, MVC layouts, partial views, and tag helpers

Teams using frontend-razor should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/frontend-razor/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/frontend/frontend-razor/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/frontend-razor/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How frontend-razor Compares

Feature / Agentfrontend-razorStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Apply when working with Razor views, MVC layouts, partial views, and tag helpers

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Razor View Development Patterns

## View Structure

### Project Layout

```
Views/
├── Shared/
│   ├── _Layout.cshtml          # Main layout template
│   ├── _LayoutEmpty.cshtml     # Minimal layout (no header/footer)
│   ├── _Header.cshtml          # Header partial
│   ├── _Footer.cshtml          # Footer partial
│   ├── _Navigation.cshtml      # Navigation partial
│   ├── _Pagination.cshtml      # Reusable pagination
│   └── Components/             # View components
│       └── SearchBox/
│           └── Default.cshtml
├── Home/
│   ├── Index.cshtml
│   └── About.cshtml
├── Products/
│   ├── Index.cshtml
│   ├── Details.cshtml
│   └── _ProductCard.cshtml     # Page-specific partial
├── _ViewImports.cshtml         # Shared imports and tag helpers
└── _ViewStart.cshtml           # Default layout assignment
```

### _ViewImports.cshtml

```cshtml
@using MyApp.Web
@using MyApp.Web.Models
@using MyApp.Core.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, MyApp.Web
```

### _ViewStart.cshtml

```cshtml
@{
    Layout = "_Layout";
}
```

## Layout Templates

### Main Layout

```cshtml
@* Views/Shared/_Layout.cshtml *@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - My Application</title>
    
    @* Head section for page-specific styles *@
    @await RenderSectionAsync("Styles", required: false)
    
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
</head>
<body class="@ViewData["BodyClass"]">
    <header>
        @await Html.PartialAsync("_Header")
        @await Html.PartialAsync("_Navigation")
    </header>
    
    <main class="container">
        @RenderBody()
    </main>
    
    <footer>
        @await Html.PartialAsync("_Footer")
    </footer>
    
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    
    @* Scripts section for page-specific JavaScript *@
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>
```

### Page Using Layout

```cshtml
@model ProductListViewModel
@{
    ViewData["Title"] = "Products";
    ViewData["BodyClass"] = "products-page";
}

@section Styles {
    <link rel="stylesheet" href="~/css/products.css" asp-append-version="true" />
}

<h1>@ViewData["Title"]</h1>

<div class="product-grid">
    @foreach (var product in Model.Products)
    {
        @await Html.PartialAsync("_ProductCard", product)
    }
</div>

@section Scripts {
    <script src="~/js/products.js" asp-append-version="true"></script>
}
```

### Nested Layouts

```cshtml
@* Views/Shared/_LayoutAdmin.cshtml *@
@{
    Layout = "_Layout";
}

<div class="admin-container">
    <aside class="admin-sidebar">
        @await Html.PartialAsync("_AdminNav")
    </aside>
    <div class="admin-content">
        @RenderBody()
    </div>
</div>

@section Scripts {
    <script src="~/js/admin.js" asp-append-version="true"></script>
    @await RenderSectionAsync("AdminScripts", required: false)
}
```

## Partial Views

### Rendering Partials

```cshtml
@* Async (recommended) *@
@await Html.PartialAsync("_ProductCard", product)

@* With explicit view path *@
@await Html.PartialAsync("~/Views/Shared/_Header.cshtml")

@* Synchronous (avoid for I/O operations) *@
@Html.Partial("_Sidebar")

@* As tag helper *@
<partial name="_ProductCard" model="product" />

@* With view-data *@
<partial name="_Pagination" model="Model.Pagination" view-data="ViewData" />
```

### Partial View Example

```cshtml
@* Views/Products/_ProductCard.cshtml *@
@model Product

<article class="product-card">
    <a asp-action="Details" asp-route-id="@Model.Id" class="product-card__link">
        <img src="@Model.ImageUrl" 
             alt="@Model.Name" 
             class="product-card__image" />
        <div class="product-card__content">
            <h3 class="product-card__title">@Model.Name</h3>
            <p class="product-card__price">@Model.Price.ToString("C")</p>
            @if (Model.IsOnSale)
            {
                <span class="product-card__badge">Sale</span>
            }
        </div>
    </a>
</article>
```

## View Components

### Component Class

```csharp
// ViewComponents/RecentArticlesViewComponent.cs
public class RecentArticlesViewComponent : ViewComponent
{
    private readonly IArticleService _articleService;

    public RecentArticlesViewComponent(IArticleService articleService)
    {
        _articleService = articleService;
    }

    public async Task<IViewComponentResult> InvokeAsync(int count = 5)
    {
        var articles = await _articleService.GetRecentAsync(count);
        return View(articles);
    }
}
```

### Component View

```cshtml
@* Views/Shared/Components/RecentArticles/Default.cshtml *@
@model IEnumerable<Article>

<section class="recent-articles">
    <h3>Recent Articles</h3>
    <ul>
        @foreach (var article in Model)
        {
            <li>
                <a asp-controller="Articles" 
                   asp-action="Details" 
                   asp-route-slug="@article.Slug">
                    @article.Title
                </a>
                <time datetime="@article.PublishedDate.ToString("yyyy-MM-dd")">
                    @article.PublishedDate.ToString("MMM dd, yyyy")
                </time>
            </li>
        }
    </ul>
</section>
```

### Invoking Components

```cshtml
@* Tag helper syntax (preferred) *@
<vc:recent-articles count="5" />

@* Async syntax *@
@await Component.InvokeAsync("RecentArticles", new { count = 5 })

@* With cache *@
<cache expires-after="@TimeSpan.FromMinutes(10)">
    <vc:recent-articles count="5" />
</cache>
```

## Tag Helpers

### Built-in Tag Helpers

```cshtml
@* Anchor tag helper *@
<a asp-controller="Products" 
   asp-action="Details" 
   asp-route-id="@product.Id"
   asp-route-category="@product.Category">
    View Details
</a>

@* Form tag helpers *@
<form asp-controller="Contact" asp-action="Submit" method="post">
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    
    <div class="form-group">
        <label asp-for="Email"></label>
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"></span>
    </div>
    
    <div class="form-group">
        <label asp-for="Category"></label>
        <select asp-for="Category" asp-items="Model.Categories" class="form-control">
            <option value="">Select a category</option>
        </select>
        <span asp-validation-for="Category" class="text-danger"></span>
    </div>
    
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

@* Image with cache busting *@
<img src="~/images/logo.png" asp-append-version="true" alt="Logo" />

@* Environment-specific rendering *@
<environment include="Development">
    <link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
    <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
```

### Custom Tag Helper

```csharp
// TagHelpers/EmailTagHelper.cs
[HtmlTargetElement("email")]
public class EmailTagHelper : TagHelper
{
    public string Address { get; set; }
    public string Subject { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "a";
        
        var href = $"mailto:{Address}";
        if (!string.IsNullOrEmpty(Subject))
        {
            href += $"?subject={Uri.EscapeDataString(Subject)}";
        }
        
        output.Attributes.SetAttribute("href", href);
        output.Content.SetContent(Address);
    }
}
```

```cshtml
@* Usage *@
<email address="support@example.com" subject="Help Request" />
```

## Model Binding

### Strongly-Typed Views

```cshtml
@model ContactFormViewModel

<form asp-action="Submit" method="post">
    @Html.AntiForgeryToken()
    
    <div class="form-group">
        <label asp-for="Name" class="form-label"></label>
        <input asp-for="Name" class="form-control" placeholder="Your name" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    
    <div class="form-group">
        <label asp-for="Email" class="form-label"></label>
        <input asp-for="Email" class="form-control" type="email" />
        <span asp-validation-for="Email" class="text-danger"></span>
    </div>
    
    <div class="form-group">
        <label asp-for="Message" class="form-label"></label>
        <textarea asp-for="Message" class="form-control" rows="5"></textarea>
        <span asp-validation-for="Message" class="text-danger"></span>
    </div>
    
    <button type="submit" class="btn btn-primary">Send Message</button>
</form>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}
```

### ViewModel with Validation

```csharp
public class ContactFormViewModel
{
    [Required(ErrorMessage = "Name is required")]
    [StringLength(100, ErrorMessage = "Name cannot exceed 100 characters")]
    [Display(Name = "Full Name")]
    public string Name { get; set; }

    [Required(ErrorMessage = "Email is required")]
    [EmailAddress(ErrorMessage = "Please enter a valid email address")]
    public string Email { get; set; }

    [Required(ErrorMessage = "Message is required")]
    [StringLength(2000, MinimumLength = 10, 
        ErrorMessage = "Message must be between 10 and 2000 characters")]
    public string Message { get; set; }
}
```

## Conditional Rendering

### If/Else Statements

```cshtml
@if (Model.Products.Any())
{
    <div class="product-grid">
        @foreach (var product in Model.Products)
        {
            <partial name="_ProductCard" model="product" />
        }
    </div>
}
else
{
    <div class="empty-state">
        <p>No products found.</p>
        <a asp-action="Index" asp-controller="Home" class="btn btn-link">
            Return to Home
        </a>
    </div>
}
```

### Switch Statements

```cshtml
@switch (Model.Status)
{
    case OrderStatus.Pending:
        <span class="badge badge-warning">Pending</span>
        break;
    case OrderStatus.Processing:
        <span class="badge badge-info">Processing</span>
        break;
    case OrderStatus.Shipped:
        <span class="badge badge-primary">Shipped</span>
        break;
    case OrderStatus.Delivered:
        <span class="badge badge-success">Delivered</span>
        break;
    default:
        <span class="badge badge-secondary">Unknown</span>
        break;
}
```

### Null Checking

```cshtml
@* Null conditional *@
<p>Author: @Model.Author?.Name ?? "Unknown"</p>

@* With fallback *@
@if (Model.ImageUrl != null)
{
    <img src="@Model.ImageUrl" alt="@Model.Title" />
}
else
{
    <img src="~/images/placeholder.jpg" alt="No image available" />
}

@* Ternary operator *@
<div class="@(Model.IsActive ? "active" : "inactive")">
    @Model.Title
</div>
```

## Loops and Collections

### For Loop

```cshtml
@for (int i = 0; i < Model.Items.Count; i++)
{
    <div class="item @(i % 2 == 0 ? "even" : "odd")">
        <span class="item-number">@(i + 1)</span>
        <span class="item-name">@Model.Items[i].Name</span>
    </div>
}
```

### Foreach with Index

```cshtml
@{ var index = 0; }
@foreach (var item in Model.Items)
{
    <div class="item" data-index="@index">
        @item.Name
    </div>
    index++;
}
```

### Rendering Lists

```cshtml
<ul class="breadcrumb">
    @foreach (var crumb in Model.Breadcrumbs)
    {
        var isLast = crumb == Model.Breadcrumbs.Last();
        <li class="breadcrumb-item @(isLast ? "active" : "")">
            @if (isLast)
            {
                @crumb.Title
            }
            else
            {
                <a href="@crumb.Url">@crumb.Title</a>
            }
        </li>
    }
</ul>
```

## HTML Helpers vs Tag Helpers

### Prefer Tag Helpers

```cshtml
@* HTML Helper (older approach) *@
@Html.ActionLink("Details", "Details", "Products", new { id = product.Id }, new { @class = "btn btn-link" })

@* Tag Helper (modern, preferred) *@
<a asp-controller="Products" asp-action="Details" asp-route-id="@product.Id" class="btn btn-link">
    Details
</a>

@* HTML Helper for form *@
@Html.TextBoxFor(m => m.Name, new { @class = "form-control", placeholder = "Enter name" })

@* Tag Helper (cleaner) *@
<input asp-for="Name" class="form-control" placeholder="Enter name" />
```

### When to Use HTML Helpers

```cshtml
@* Complex dynamic attributes *@
@Html.TextBoxFor(m => m.Name, Model.GetInputAttributes())

@* Raw HTML content *@
@Html.Raw(Model.HtmlContent)

@* Display templates *@
@Html.DisplayFor(m => m.CreatedDate)

@* Editor templates *@
@Html.EditorFor(m => m.Address)
```

## ViewData, ViewBag, and TempData

### ViewData (Dictionary)

```cshtml
@* In Controller *@
ViewData["Title"] = "Product Details";
ViewData["ShowSidebar"] = true;

@* In View *@
<h1>@ViewData["Title"]</h1>
@if ((bool?)ViewData["ShowSidebar"] == true)
{
    <partial name="_Sidebar" />
}
```

### ViewBag (Dynamic)

```cshtml
@* In Controller *@
ViewBag.Categories = await _categoryService.GetAllAsync();

@* In View *@
<select asp-items="@(new SelectList(ViewBag.Categories, "Id", "Name"))">
    <option value="">All Categories</option>
</select>
```

### TempData (Survives Redirect)

```csharp
// In Controller
TempData["SuccessMessage"] = "Product saved successfully!";
return RedirectToAction("Index");
```

```cshtml
@* In View *@
@if (TempData["SuccessMessage"] != null)
{
    <div class="alert alert-success alert-dismissible">
        @TempData["SuccessMessage"]
        <button type="button" class="close" data-dismiss="alert">&times;</button>
    </div>
}
```

## AJAX and Partial Rendering

### AJAX Form Submission

```cshtml
<form id="contact-form" asp-action="SubmitAjax" method="post">
    @Html.AntiForgeryToken()
    
    <div class="form-group">
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

<div id="result"></div>

@section Scripts {
    <script>
    $('#contact-form').on('submit', function(e) {
        e.preventDefault();
        
        var $form = $(this);
        var $button = $form.find('button[type="submit"]');
        
        $button.prop('disabled', true).text('Sending...');
        
        $.ajax({
            url: $form.attr('action'),
            type: 'POST',
            data: $form.serialize(),
            success: function(response) {
                $('#result').html(response);
                $form[0].reset();
            },
            error: function(xhr) {
                $('#result').html('<div class="alert alert-danger">An error occurred.</div>');
            },
            complete: function() {
                $button.prop('disabled', false).text('Submit');
            }
        });
    });
    </script>
}
```

### Returning Partial Views from Controller

```csharp
[HttpGet]
public async Task<IActionResult> LoadMore(int page)
{
    var products = await _productService.GetPagedAsync(page, 10);
    return PartialView("_ProductGrid", products);
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SubmitAjax(ContactFormViewModel model)
{
    if (!ModelState.IsValid)
    {
        return PartialView("_ContactFormErrors", model);
    }
    
    await _contactService.ProcessAsync(model);
    return PartialView("_ContactFormSuccess");
}
```

## Accessibility Considerations

```cshtml
@* Semantic HTML with ARIA *@
<nav aria-label="Main navigation">
    <ul class="nav" role="menubar">
        @foreach (var item in Model.NavItems)
        {
            <li role="none">
                <a href="@item.Url" 
                   role="menuitem"
                   aria-current="@(item.IsActive ? "page" : null)">
                    @item.Title
                </a>
            </li>
        }
    </ul>
</nav>

@* Form accessibility *@
<div class="form-group">
    <label asp-for="Email" id="email-label"></label>
    <input asp-for="Email" 
           aria-labelledby="email-label" 
           aria-describedby="email-help email-error" />
    <small id="email-help" class="form-text text-muted">
        We'll never share your email.
    </small>
    <span asp-validation-for="Email" id="email-error" class="text-danger" role="alert"></span>
</div>

@* Skip link for keyboard navigation *@
<a href="#main-content" class="skip-link">Skip to main content</a>
```

Related Skills

ring:dev-refactor-frontend

16
from diegosouzapw/awesome-omni-skill

Analyzes frontend codebase against Ring standards and generates refactoring tasks for ring:dev-cycle-frontend. Dispatches frontend-specific agents in ANALYSIS mode.

rcr-frontend

16
from diegosouzapw/awesome-omni-skill

Component development rules specific to Red Cliff Record. Use when working with React components, Tailwind CSS styling, Radix/Shadcn primitives, icons, buttons, forms, or frontend code in this project. Triggers on component files, styling questions, design tokens, Tailwind v4, Shadcn, Radix, TanStack Forms, Lucide icons, or UI primitive usage patterns (sizing, spacing, layout).

moai-domain-frontend

16
from diegosouzapw/awesome-omni-skill

Frontend development specialist covering React 19, Next.js 16, Vue 3.5, and modern UI/UX patterns with component architecture. Use when building web UIs, implementing components, optimizing frontend performance, or integrating state management.

Frontend Verification & Testing

16
from diegosouzapw/awesome-omni-skill

Verify and test Angular 18 frontend changes using Chrome DevTools MCP. Automatically check console errors, network requests, and visual rendering after implementing tasks or when fixing UI bugs. Use when creating components, debugging visual issues, validating API integration, or ensuring UI requirements are met. File types: .ts, .html, .css, .scss

frontend-trends-2026

16
from diegosouzapw/awesome-omni-skill

Collection of 2026 Frontend Design Formulas (Liquid Glass, Bento, Neo-Brutalism, Eco-Dark).

frontend-react-testing-strategy

16
from diegosouzapw/awesome-omni-skill

Standardized guidelines and patterns for Frontend React Testing Strategy.

Frontend Pages

16
from diegosouzapw/awesome-omni-skill

Create or modify React pages using MUI components, React Router, and the HATEOAS API client.

Frontend Development

16
from diegosouzapw/awesome-omni-skill

พัฒนา Frontend ด้วย Angular, React, Vue, Next.js อย่างมืออาชีพ

frontend-design-ultimate

16
from diegosouzapw/awesome-omni-skill

Create distinctive, production-grade static sites with React, Tailwind CSS, and shadcn/ui — no mockups needed. Generates bold, memorable designs from plain text requirements with anti-AI-slop aesthetics, mobile-first responsive patterns, and single-file bundling. Use when building landing pages, marketing sites, portfolios, dashboards, or any static web UI. Supports both Vite (pure static) and Next.js (Vercel deploy) workflows.

frontend-design

16
from diegosouzapw/awesome-omni-skill

UI/UX design patterns using DaisyUI v5 and TailwindCSS for Splits Network

frontend-design-fixlify

16
from diegosouzapw/awesome-omni-skill

Create distinctive, production-grade frontend interfaces for Fixlify. Automatically activates when building UI components, pages, dashboards, forms, or any visual interface. Uses Fixlify design system with shadcn/ui, Tailwind CSS, and React patterns.

frontend-architect

16
from diegosouzapw/awesome-omni-skill

Build production-grade UI/UX with React (Next.js, Docusaurus), CSS architecture (Tailwind, Modules, Global), animations, theming, performance optimization, state management, and testing. Use when creating React components, building layouts, refactoring CSS, implementing animations, auditing accessibility, optimizing performance, setting up state management, or writing component tests.