Meet Semantic Components — A Modern Angular UI Library — DeepSeek Blog | Neura Market
    Neura MarketNeura Market/DeepSeek
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeek
    CoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    View All Directories
    OverviewRulesPromptsMCPsAgentsBlogVideosGuidesCoursesCommunityTrendingGenerate
    DeepSeekBlogMeet Semantic Components — A Modern Angular UI Library
    Back to Blog
    Meet Semantic Components — A Modern Angular UI Library
    angular

    Meet Semantic Components — A Modern Angular UI Library

    khalil la February 25, 2026
    0 views

    After waiting so long for an Angular UI library that actually met my needs, I decided to stop waiting...

    After waiting so long for an Angular UI library that actually met my needs, I decided to stop waiting and build my own. The result is **Semantic Components** — an open-source Angular UI library built on Tailwind CSS, Angular CDK, and Angular Aria, heavily inspired by [shadcn/ui](https://ui.shadcn.com). **GitHub:** https://github.com/gridatek/semantic-components **Package:** `@semantic-components/ui` **Website:** https://semantic-components.com --- ## Why Semantic Components? The Angular ecosystem has always had fewer off-the-shelf UI options compared to React. Libraries like shadcn/ui, Radix, and Headless UI have raised the bar for what a component library can be — and Angular deserves the same quality. Semantic Components is my attempt to bring that standard to Angular, while leaning fully into what makes Angular great. --- ## Core Design Principles ### Semantic Every directive or component is named to describe its **role** in the interface, not just the feature it belongs to. Take the tooltip as an example. Angular Material gives you a single `matTooltip` directive: ```html <!-- Angular Material --> <button matTooltip="Save changes">Save</button> ``` In Semantic Components, it's scTooltipTrigger: ```html <!-- Semantic Components --> <button scTooltipTrigger="Save changes">Save</button> ``` `scTooltipTrigger` — because `ScTooltip` is already the component that _renders_ the actual tooltip bubble. The directive on the button is not the tooltip — it's what triggers it. These are two different things, and the names reflect that. `ScDrawerTrigger`, `ScSelectValue`, `ScSelectTrigger`, `ScSidebarBody` — you know exactly what each piece does before reading a single line of docs. This principle extends to the **HTML elements themselves**. When possible, components/directives are applied to the right native element rather than a generic `<div>`. ### Declarative The entire UI is described in the template — no imperative `open()`, `close()`, or `DialogService.create()` calls. Take the dialog as an example: ```html <div scDialogProvider [(open)]="isOpen"> <button scDialogTrigger scButton variant="outline">Open Dialog</button> <ng-template scDialogPortal> <div scDialog> <button scDialogClose> <svg siXIcon></svg> <span class="sr-only">Close</span> </button> <div scDialogHeader> <h2 scDialogTitle>Edit profile</h2> <p scDialogDescription>Make changes to your profile here.</p> </div> <!-- content --> <div scDialogFooter> <button scButton variant="outline" (click)="isOpen.set(false)">Cancel</button> <button scButton type="submit">Save changes</button> </div> </div> </ng-template> </div> ``` ```typescript readonly isOpen = signal(false); ``` The open state is a signal. The trigger, the portal, the close button — all declared in the template. No service injection, no imperative show/hide, no `ViewContainerRef` gymnastics. You read the template and immediately understand the full structure of the dialog. The naming reinforces this. `ScDialog` is not a service — it's the `<div role="dialog">` element itself. In Angular Material, `MatDialog` is a service you inject and call `.open()` on. Here, `scDialog` is the thing rendered in the DOM. Same naming principle: the name describes exactly what the piece _is_, not what it _does behind the scenes_. There is a tradeoff: `scDialog` requires an extra wrapper element in the DOM `scDialogProvider`. It acts as the coordination point between the trigger, the portal, and the close button — sharing state through Angular's DI tree. It's a conscious choice in favor of keeping everything in the template, at the cost of one extra `<div>` that you may need to style or account for in your layout. ### Composable Each component is a set of small, focused pieces that you assemble yourself. There are no magic `[content]` inputs or hidden `<ng-content>` slots — you write the structure, and the pieces plug into it. The Select is a good example of how far this goes: ```html <div scSelect #select="scSelect" placeholder="Select a label"> <div scSelectTrigger aria-label="Label dropdown"> <span scSelectValue> @if (displayIcon(); as icon) { <svg scSelectItemIcon siTagIcon></svg> } <span class="truncate">{{ select.displayValue() }}</span> </span> </div> <ng-template scSelectPortal> <div scSelectPopup> <div scSelectList> @for (item of items; track item.value) { <div scSelectItem [value]="item.value" [label]="item.label"> <svg scSelectItemIcon siTagIcon></svg> <span>{{ item.label }}</span> </div> } </div> </div> </ng-template> </div> ``` - **You own the structure** — the trigger layout, the item layout, the icons, the display value - **You extend freely** — want a custom empty state in the list? A header above the items? Just add it - **The library handles behavior** — keyboard navigation, selection state, ARIA attributes — you handle the markup This also composes across components. A button can be a drawer trigger, a tooltip trigger, and an icon button all at once: ```html <button scButton size="icon" scDrawerTrigger scTooltipTrigger="Open menu"> <svg siMenuIcon></svg> </button> ``` One element. Three responsibilities. No wrappers. The tradeoff is verbosity. Because you own the structure, you write more template code than you would with a batteries-included component that hides everything behind inputs. That's a deliberate choice — explicit over implicit. You always know what's in the DOM because you put it there. ### Tailwind + CVA for Variants The library follows the [shadcn/ui](https://ui.shadcn.com) design system — same CSS variables, same color tokens (`bg-primary`, `text-muted-foreground`, `border-input`…), same default styles. If you're already familiar with shadcn, the visual language is instantly recognizable. Styles are written in Tailwind CSS and managed with [class-variance-authority](https://cva.style). This means: - Predictable, overridable class names - Consistent variants (`default`, `outline`, `ghost`, `destructive`, `link`) across all components ```typescript export const buttonVariants = cva('inline-flex items-center justify-center rounded-lg border ...', { variants: { variant: { default: 'bg-primary text-primary-foreground', outline: 'border-border bg-background hover:bg-muted', ghost: 'hover:bg-muted hover:text-foreground', destructive: 'bg-destructive/10 text-destructive', link: 'text-primary underline-offset-4 hover:underline', }, size: { default: 'h-8 px-2.5', sm: 'h-7 px-2.5 text-[0.8rem]', lg: 'h-9 px-2.5', icon: 'size-8', }, }, }); ``` ### Built on Solid Foundations The rest of the library's design is guided by a few core principles: **Attribute selectors over element selectors.** Instead of custom elements like `<sc-button>`, the library uses attribute selectors on native HTML. No extra wrapper elements, native accessibility roles preserved, and multiple components/directives can stack on the same element: ```html <button scButton variant="outline" scDrawerTrigger>Open</button> ``` **Modern Angular, all the way down.** Signals (`input()`, `output()`, `computed()`), standalone components, native control flow (`@if`, `@for`), `inject()`, and OnPush everywhere. Overlays and positioning are built on `@angular/cdk`. Accessible patterns like focus trapping and live regions use `@angular/cdk/a11y` and `@angular/aria`. Forms are signal-based. The library is also zoneless-compatible — no `zone.js` required. No legacy APIs, no NgModules. ```typescript @Directive({ selector: 'button[scButton]' }) export class ScButton { readonly variant = input<ScButtonVariants['variant']>('default'); readonly size = input<ScButtonVariants['size']>('default'); readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute }); } ``` **Accessible by default.** Every component is built to pass WCAG AA minimums — proper ARIA attributes, full keyboard navigation, focus management on dialogs and drawers, and screen reader support. Where possible, this is powered by Angular CDK's accessibility primitives (`@angular/cdk/a11y`) and `@angular/aria`. --- ## Tradeoffs This library makes deliberate choices that prioritize the future of Angular over backwards compatibility. That means it is **not for every project** — and that's intentional. - **Zoneless only.** The library is built for zoneless Angular apps. - **OnPush only.** All components use `ChangeDetectionStrategy.OnPush`. - **Signal-based forms only.** Form integrations are designed around signals, not `NgModel` or reactive forms. - **No NgModules.** Everything is standalone. There are no module exports, no `forRoot()`, no compatibility shims for module-based apps. --- ## What's in the Box ### `@semantic-components/ui` — Core Library 50+ components: | Category | Components | | ---------- | ---------------------------------------------------------------------------------------------------------------------- | | Actions | Button, Button Group, Link, Toggle, Toggle Group | | Layout | Card, Separator, Aspect Ratio, Toolbar, Scroll Area, Typography | | Forms | Input, Textarea, Checkbox, Radio Group, Switch, Select, Native Select, Label, Field, Input Group, Slider, Range Slider | | Overlays | Dialog, Alert Dialog, Drawer, Sheet, Popover, Hover Card, Tooltip, Toast, Backdrop | | Navigation | Breadcrumb, Pagination, Tabs, Menu, Menu Bar, Navigation Menu | | Display | Alert, Badge, Avatar, Skeleton, Spinner, Progress, Kbd, Empty, Item | | Data | Table, Accordion, Collapsible, Calendar, Date Picker, Time Picker | | File | File Upload | --- ## Icons: `@semantic-icons/lucide-icons` Icons are distributed as Angular components from `@semantic-icons/lucide-icons`. Every icon is a standalone component you apply to an `<svg>` element: ```html <svg siStarIcon></svg> <svg siUserIcon></svg> <svg siArrowRightIcon></svg> ``` This approach is fully tree-shakable — only the icons you import end up in your bundle. No icon fonts, no sprite sheets. --- ## Getting Started ```bash npm install @semantic-components/ui ``` Add the styles to your global stylesheet: ```css @import '@semantic-components/ui/styles'; @source "../node_modules/@semantic-components/ui"; ``` The `@import` brings in the CDK overlay styles and the shadcn-compatible CSS variables (colors, radius, spacing tokens). The `@source` tells Tailwind v4 to scan the library's files so its utility classes are included in your build. Then import what you need directly in your standalone component: ```typescript import { ScButton, ScDialog, ScDialogBody, ScDialogTitle } from '@semantic-components/ui'; @Component({ imports: [ScButton, ScDialog, ScDialogBody, ScDialogTitle], template: ` <button scButton>Open Dialog</button> `, }) export class MyComponent {} ``` No module registration. No `forRoot()`. Just import and use. --- ## Links - **GitHub:** https://github.com/gridatek/semantic-components - **npm:** https://www.npmjs.com/package/@semantic-components/ui - **License:** MIT Feedback, stars, and contributions are very welcome. If you're building Angular apps and tired of fighting your UI library, give Semantic Components a try.

    Tags

    angularuitailwindcssshadcn

    Comments

    More Blog

    View all
    How I'm using ASTs and Gemini to solve the "Codebase Onboarding" problem 🧠ai

    How I'm using ASTs and Gemini to solve the "Codebase Onboarding" problem 🧠

    Hi everyone! 👋 I’m Tara, a Senior Software Engineer and Consultant. Over the years, I've jumped...

    T
    tworrell
    Local AI Will Save Us All (The Math Says So, Trust Me)ai

    Local AI Will Save Us All (The Math Says So, Trust Me)

    Every few weeks a take goes viral in tech circles making the case for ditching cloud AI and running...

    S
    Sebastian Schürmann
    Lost in the AI Hype, I Started Smallai

    Lost in the AI Hype, I Started Small

    And it helped me get back into tech without drowning TL;DR at the end Coming back to...

    R
    Rohini Gaonkar
    Building a Replay-Tested Interactive Brokers Client in Gogo

    Building a Replay-Tested Interactive Brokers Client in Go

    I wanted an IBKR library that felt like Go and had testing I could trust. So I wrote one.

    T
    Thomas Marcelis
    Playwright in Pictures: Fully Parallel Modeplaywright

    Playwright in Pictures: Fully Parallel Mode

    Playwright’s fullyParallel mode is often treated as a simple performance switch. In practice, it...

    V
    Vitaliy Potapov
    Designing a CLI for Both Humans and Agentscli

    Designing a CLI for Both Humans and Agents

    Learn how Alpic designed its CLI for both human developers and AI agents — covering tradeoffs like polling, context windows, interactivity, and statelessness.

    J
    Julien Vallini

    Stay up to date

    Get the latest DeepSeek prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for DeepSeek and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.