Meet Semantic Components — A Modern Angular UI Library —…
    Neura MarketNeura Market/DeepSeek
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeek
    CoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    View All Directories
    OverviewRulesPromptsMCPsAgentsGamesBlogVideosGuidesCoursesCommunityTrending
    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.

    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:

    <!-- Angular Material -->
    <button matTooltip="Save changes">Save</button>
    

    In Semantic Components, it's scTooltipTrigger:

    <!-- 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:

    <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>
    
    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:

    <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:

    <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 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. This means:

    • Predictable, overridable class names
    • Consistent variants (default, outline, ghost, destructive, link) across all components
    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:

    <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.

    @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:

    CategoryComponents
    ActionsButton, Button Group, Link, Toggle, Toggle Group
    LayoutCard, Separator, Aspect Ratio, Toolbar, Scroll Area, Typography
    FormsInput, Textarea, Checkbox, Radio Group, Switch, Select, Native Select, Label, Field, Input Group, Slider, Range Slider
    OverlaysDialog, Alert Dialog, Drawer, Sheet, Popover, Hover Card, Tooltip, Toast, Backdrop
    NavigationBreadcrumb, Pagination, Tabs, Menu, Menu Bar, Navigation Menu
    DisplayAlert, Badge, Avatar, Skeleton, Spinner, Progress, Kbd, Empty, Item
    DataTable, Accordion, Collapsible, Calendar, Date Picker, Time Picker
    FileFile 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:

    <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

    npm install @semantic-components/ui
    

    Add the styles to your global stylesheet:

    @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:

    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
    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught megemma

    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught me

    I ported the whole Gemma-4 family — E2B, E4B, 12B, 31B, and the 26B-A4B MoE — to run on...

    X
    xbill
    Hey DEV, I'm Tobore. Let's actually connect.community

    Hey DEV, I'm Tobore. Let's actually connect.

    Hey DEV, I'm Tobore. Let's actually connect. I've been on here for a while now, mostly writing and...

    L
    Laurina Ayarah
    I burned through thousands of AI tokens. Then a friend did it for freeai

    I burned through thousands of AI tokens. Then a friend did it for free

    (yep, kinda clickbait, just for the funsies 😊) At the beginning of the year, I relaunched my...

    P
    Paulo Henrique
    Claude might be saturating your machineai

    Claude might be saturating your machine

    My laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit...

    S
    Sidhant Panda
    Automated GitHub Code Reviews Using Google Geminigithubactions

    Automated GitHub Code Reviews Using Google Gemini

    I Built a Thing! TL;DR — Google Gemini-based Pull Request reviews and Issue Triaging for...

    D
    Darren "Dazbo" Lester
    What is an "agentic harness," actually?ai

    What is an "agentic harness," actually?

    I've been hearing the word "harness" thrown around a lot lately. I assumed it just meant "the IDE" or...

    T
    Tilde A. Thurium

    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.

    Neura Market

    Custom AI Systems & Services

    Our team of experienced AI builders will help build custom AI systems, workflows, and solutions for your business.

    Request custom work

    Ready-made automations for this

    Workflows from the Neura Market marketplace related to this DeepSeek resource

    • Automate Blog Content Creation with Notion MCP, DeepSeek AI, and WordPressn8n · $9.99 · Related topic
    • Generate AI Videos from Scripts with DeepSeek, Synthesia, and Together.ain8n · $24.99 · Related topic
    • Compare Multi-Period Financial Data from Google Sheets with DeepSeek AI Analysisn8n · $14.99 · Related topic
    • PostgreSQL Conversational Agent with Claude & DeepSeek (Multi-KPI, Secure)n8n · $14.99 · Related topic
    Browse all workflows