Button
buttonuiInteractive button component with variants (default, destructive, outline, secondary, ghost, link), sizes, and standalone Angular API.
Installation
$pnpm dlx uipkge-ng@latest add button$npx uipkge-ng@latest add button$yarn dlx uipkge-ng@latest add button$bunx uipkge-ng@latest add buttonManifest:
https://uipkge.dev/r/angular/button.jsonInstalls to:src/app/components/ui/button/Examples
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
variant | 'default''destructive''outline''secondary''ghost''link' | default | optional |
size | 'default''sm''lg''xs''icon''icon-sm''icon-lg' | default | optional |
type | 'button''submit''reset' | button | optional |
disabled | boolean | false | optional |
class | string | — | optional |
npm dependencies
Used by
ab-test-variant-resultsaccess-request-workflowad-spend-roi-dashboardagent-memory-inspectorai-agent-orchestratorai-code-review-assistantai-crm-pipeline-dashboardai-llm-chatai-saas-command-centerairbnb-host-earningsalert-modalannouncement-banner-splitannouncement-corner-toastannouncement-countdown-barannouncement-rotating-stripapi-documentation-pageapi-keysarticle-editor-toolbarasset-tracking-mapassignment-submission-dropzoneaudio-waveform-transcriptaudit-event-streamaudit-logauth-mfaauth-password-resetauth-sign-inauth-sign-upauthor-bio-boxbackorder-split-shipment-managerbank-account-connectorbefore-after-side-by-sidebefore-after-togglebenefits-enrollment-portalbento-01bento-asymmetric-gridbento-metrics-mixedbill-of-lading-viewerbilling-accountbilling-usage-centerblog-post-card-gridboard-resolution-signoffbookmark-manager-gridcalendarcap-table-ownership-summarycareers-job-boardcarouselcart-drawercase-studycase-study-featured-storycase-study-filterable-listcase-study-metric-cardscase-study-metric-comparisoncase-study-results-stripcash-flow-treasury-dashboardcategory-showcasecdc-replication-streamcertificate-credential-cardchangelogchat-threadchat-two-panecheckout-flowcloud-backup-schedulecode-blockcode-diff-reviewcode-install-commandcode-snippet-playgroundcode-tabs-multi-languagecode-terminal-sessioncold-chain-temp-monitorcolumn-profiling-summarycoming-sooncommercial-invoice-generatorcommute-reimbursement-claimscomparison-migration-pathcomparison-tablecomparison-vs-alternativescompensation-band-benchmarkingcompliance-sanctions-screenercompliance-soc2-readinesscontact-01contact-uscontent-calendar-matrixcontract-redline-diffcontractor-timesheet-approvalcookie-consentcookie-consent-bannercookie-consent-barcookie-consent-preferencescourse-curriculum-outlinecrm-sales-call-workbenchcron-job-schedulercrypto-wallet-portfoliocta-01cta-final-bandcta-gradient-glow-actioncta-split-demo-request-modalcta-split-panelcurrency-converter-widgetcustomer-journey-mapcustomer-reviewscustoms-clearance-trackerdaily-standup-summarydashboard-layoutdata-catalog-lineagedata-contract-governancedata-explorerdata-mesh-domain-catalogdata-product-carddata-quality-metricsdata-retention-policy-managerdata-table-pagedatabase-migration-schema-diffdatabase-schema-viewerdate-pickerdbt-model-graphdecision-matrix-tabledefi-staking-yield-vaultdelivery-proof-carddelivery-tracking-mapdemo-guided-tourdemo-sandbox-paneldeployment-pipelinedialogdisaster-response-mapdispatch-assignment-boarddns-record-managerdoctor-appointment-schedulerdriver-inspection-checklistemail-campaign-composeremail-template-previeweremployee-org-chart-treeemployee-pulse-survey-resultsempty-statesenterprise-workspace-switcherenvironment-variables-managererror-404error-500error-pageerror-pagesetl-pipeline-dagevent-calendarevent-calendar-dashboardexit-intentexit-intent-newsletterexit-intent-offerexit-intent-surveyexit-interview-surveyfaq-01faq-categorized-tabsfaq-contact-sidebarfaq-searchable-accordion-workbenchfaq-two-columnfeature-accordion-mediafeature-alternating-rowsfeature-bento-ai-copilotfeature-code-preview-splitfeature-comparison-sliderfeature-drag-drop-organizerfeature-flags-managerfeature-full-bleed-rowsfeature-hover-tilt-cardsfeature-interactive-api-curl-builderfeature-metric-growth-stepperfeature-segmented-comparefeature-sticky-media-scrollfeature-store-registryfeature-tabbed-deepdivefeature-vertical-tabs-panefeatures-01features-2x2-spotlightfeatures-checklist-splitfeatures-icon-list-densefeatures-numbered-gridfield-inspection-managerfile-managerfine-tuning-job-monitorflash-sale-offer-barflashcard-study-deckflight-radar-mapfloor-plan-explorerfooterfooter-01footer-brand-statementfooter-developer-dense-gridfooter-mega-newsletterfooter-minimal-rowfooter-sitemap-denseform-layoutsfreelance-work-for-hire-contractfreight-quote-calculatorfrequently-bought-togetherganttgift-card-balance-checkergithub-oss-traction-bandgraphql-query-explorerguardrails-safety-scannerhabit-streak-trackerheaderheader-01header-announcement-stackheader-centered-logoheader-floating-pillheader-mega-menuheader-search-commandheader-transparent-scrollherohero-01hero-3d-tilt-interactive-canvashero-ascii-terminal-streamhero-bento-preview-gridhero-developer-terminalhero-globe-interactive-nodeshero-saas-metrics-dashboardhero-split-device-mockuphero-video-modal-walkthroughhero-waitlist-glowhotel-booking-reservationhow-it-works-arrow-flowhow-it-works-before-afterhow-it-works-scroll-stepperhow-it-works-stepshow-it-works-tabbed-stepshow-it-works-timelineimmunization-historyimport-wizardinboxinfluencer-campaign-cardinsurance-eligibility-checkerintegrations-connector-statusintegrations-directoryintegrations-featured-pairsintegrations-hub-diagramintegrations-logo-gridintegrations-search-directoryinventory-reorder-forecastinvite-membersinvoice-creator-wizardinvoices-listkafka-stream-monitorkanban-task-boardknowledge-base-hublab-results-viewerlakehouse-table-optimizerlast-mile-courier-dispatchlead-capture-popupleaflet-asset-tracking-mapleaflet-delivery-tracking-mapleaflet-disaster-response-mapleaflet-flight-radar-mapleaflet-last-mile-courier-dispatchleaflet-maritime-vessel-mapleaflet-neighborhood-walkscore-cardleaflet-property-boundary-maplease-agreement-signlegal-policy-pagelesson-video-playerllm-eval-benchmark-matrixloan-calculatorlogin-01login-02logo-cloud-grayscalelogosloyalty-rewards-tier-hubmaintenance-request-ticketmaritime-vessel-mapmatrix-swot-analysismcp-server-registry-managermedia-asset-gallerymeeting-agenda-notesmodel-token-cost-optimizermultimodal-vision-playgroundnda-agreement-generatorneighborhood-walkscore-cardnewsletternewsletter-inline-bandnewsletter-issue-archivenewsletter-signupnewsletter-split-previewnotification-centernotifications-popoveronboarding-checklistonboarding-equipment-provisioningonboarding-wizardorder-confirmationorder-management-pagepacking-slip-generatorparquet-metadata-inspectorpassword-generator-widgetpatient-intake-formpatient-medical-recordpayment-formpayouts-management-tablepayroll-run-summarypeer-discussion-forumperformance-review-matrixpersonal-finance-budgetpodcast-audio-playerpomodoro-focus-timerprescription-refill-managerpress-kit-brand-assetsprice-drop-alert-cardpricingpricing-01pricing-calculatorpricing-enterprise-sla-cardpricing-feature-addon-builderpricing-feature-tier-sliderpricing-matrix-groupedpricing-matrix-plan-cardspricing-matrix-sticky-headerpricing-single-planpricing-tier-comparison-matrixpricing-usage-calculator-sliderprivacy-data-export-portalprivacy-policy-generatorproduct-comparison-drawerproduct-detail-pageproduct-grid-pageproduct-qa-communityproject-roadmapprompt-ab-testing-comparatorprompt-playgroundproperty-amenities-gridproperty-boundary-mapproperty-listing-cardpurchase-order-receivingquiz-assessment-runnerrag-pipeline-visualizerrange-calendarrate-limiting-configratings-award-wallratings-review-badgesratings-star-summaryrecord-detail-pagerecruitment-candidate-pipelinereferral-program-widgetregister-01resources-category-gridresources-featured-listresources-teaser-gridreturn-merchandise-inspectionreturns-portalreverse-etl-sync-managerroi-calculator-inputssafe-note-calculatorsaved-cards-listschema-drift-alert-boardsearch-results-pagesecrets-rotation-schedulersecurity-certification-bandsecurity-compliance-badgessecurity-data-flowsecurity-incident-timelinesecurity-trust-center-gridseo-audit-checklistservice-health-matrixsession-device-managersettings-pageshipment-tracking-timelineshipping-label-printershortcuts-dialogsidebarskill-assessment-radarsmart-inbox-triagesocial-media-post-schedulerspark-job-stage-visualizersql-query-notebooksso-identity-provider-configstats-bandsticky-ctasticky-cta-barsticky-cta-corner-buttonsticky-cta-reading-progressstock-transfer-managerstore-pickup-curbsidestream-processing-flink-topologystudent-gradebook-tablesubpoena-legal-hold-managersubscription-churn-analyticssupport-ticket-detailsynthetic-data-generatortax-summary-reportteam-sectiontechnical-specs-sheettelehealth-video-roomtenant-rent-ledgertestimonialtestimonial-quote-carousel-telemetrytestimonial-single-featuredtestimonial-social-cardstestimonials-01theme-customizetime-off-portaltime-pickertime-tracker-timesheettourtrademark-patent-portfoliotraining-compliance-trackertransaction-feedtransfertriage-queue-dashboardusage-metering-dashboarduse-casesuse-cases-industry-griduse-cases-persona-tabsuse-cases-role-cardsuse-cases-scenario-walkthroughuser-profile-pageutm-campaign-buildervector-embeddings-inspectorvendor-supplier-portalvideo-playlist-sidebarvirtual-card-managervirtual-tour-panoramavitals-tracker-chartvulnerability-scanner-reportwaitlistwaitlist-referral-positionwarehouse-inventory-gridwave-picking-consolewebhook-testerwebhookswholesale-b2b-quick-order
Files installed (4)
src/app/components/ui/button/button.component.ts2.7 kB
import { Component, ElementRef, Input, booleanAttribute, inject, ChangeDetectionStrategy } from '@angular/core' import { cn } from '@/lib/utils' import { buttonVariants, type ButtonVariants } from './button.variants' export type ButtonVariant = NonNullable<ButtonVariants['variant']> export type ButtonSize = NonNullable<ButtonVariants['size']> /** * Angular port of UIPKGE Button (React `Button`). Put it on the element you want to render * (`<button ui-button>`, `<a ui-button>`): the attribute form is the Radix `asChild` * equivalent, so an anchor keeps its own semantics and never receives `type`. On a native * `<button>` the default `type="button"` avoids accidental form submits and `disabled` sets * the native attribute. The `<ui-button>` element form gets button semantics via * role + tabindex + Enter / Space (disabled there means aria-disabled + out of the tab order; * prefer `<button ui-button>` when you need a truly inert control). Class strings come from * shared `buttonVariants`. */ @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-button, [ui-button]', standalone: true, host: { '[attr.data-uipkge]': '""', '[attr.data-slot]': '"button"', '[attr.data-variant]': 'variant', '[attr.data-size]': 'size', '[attr.type]': 'isButton ? type : null', '[attr.disabled]': 'isButton && disabled ? "" : null', '[attr.role]': 'isCustom ? "button" : null', '[attr.tabindex]': 'isCustom ? (disabled ? -1 : 0) : null', '[attr.aria-disabled]': 'isCustom && disabled ? "true" : null', '[attr.data-disabled]': 'isCustom && disabled ? "" : null', '[class]': 'hostClass', '(keydown)': 'onKeydown($event)', }, template: `<ng-content />`, }) export class UiButtonComponent { private readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement /** Native `<button>` host: gets `type` + native `disabled`. */ readonly isButton = this.el.tagName === 'BUTTON' /** `<ui-button>` custom element: needs role / tabindex / keyboard activation. */ readonly isCustom = this.el.tagName === 'UI-BUTTON' @Input() variant: ButtonVariant = 'default' @Input() size: ButtonSize = 'default' @Input() type: 'button' | 'submit' | 'reset' = 'button' @Input({ transform: booleanAttribute }) disabled = false @Input('class') className?: string get hostClass(): string { return cn(buttonVariants({ variant: this.variant, size: this.size }), this.className) } onKeydown(event: KeyboardEvent): void { if (!this.isCustom || this.disabled) return if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() this.el.click() } } } export { buttonVariants, type ButtonVariants }src/app/components/ui/button/button-group.component.ts4.8 kB
import { Component, Input, booleanAttribute, ChangeDetectionStrategy } from '@angular/core' import { cn } from '@/lib/utils' export type ButtonGroupOrientation = 'horizontal' | 'vertical' /** * Angular port of UIPKGE ButtonGroup. Segmented toolbars and split buttons * with shared borders. Class strings identical to React ButtonGroup. */ @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-button-group, [ui-button-group]', standalone: true, host: { '[attr.data-slot]': '"button-group"', '[attr.data-uipkge]': '""', '[attr.role]': '"group"', '[attr.data-orientation]': 'orientation', '[attr.data-attached]': 'attachedAttr', '[class]': 'hostClass', }, template: `<ng-content />`, }) export class UiButtonGroupComponent { @Input() orientation: ButtonGroupOrientation = 'horizontal' @Input({ transform: booleanAttribute }) attached = true @Input('class') className?: string get attachedAttr(): string | undefined { return this.attached ? '' : undefined } get hostClass(): string { const orientation = this.orientation const attached = this.attached return cn( 'inline-flex items-center', orientation === 'vertical' ? 'flex-col items-stretch' : 'flex-row', // The `.contents>button` rules reach a trigger button inside a layout-transparent wrapper // root (<ui-dropdown-menu>, <ui-popover>, ...), which React's Radix roots don't render. // Its data-slot is the trigger's, as in React, so only the plain `button` rules apply. attached && [ '[&>[data-slot=button]]:relative [&>[data-slot=button]:focus-visible]:z-20 [&>[data-slot=button]:hover]:z-10', '[&>button]:relative [&>button:focus-visible]:z-20 [&>button:hover]:z-10', '[&>.contents>button]:relative [&>.contents>button:focus-visible]:z-20 [&>.contents>button:hover]:z-10', orientation === 'horizontal' && [ '[&>[data-slot=button]]:rounded-none [&>button]:rounded-none', '[&>.contents>button]:rounded-none', '[&>[data-slot=button]:first-child]:rounded-l-md [&>button:first-child]:rounded-l-md', '[&>.contents:first-child>button]:rounded-l-md', '[&>[data-slot=button]:last-child]:rounded-r-md [&>button:last-child]:rounded-r-md', '[&>.contents:last-child>button]:rounded-r-md', '[&>[data-slot=button]:only-child]:rounded-md [&>button:only-child]:rounded-md', '[&>.contents:only-child>button]:rounded-md', '[&>[data-slot=button]:not(:first-child)]:-ml-px [&>button:not(:first-child)]:-ml-px', '[&>.contents:not(:first-child)>button]:-ml-px', '[&>[data-slot=button][data-variant=default]:not(:first-child)]:border-primary-foreground/20 [&>[data-slot=button][data-variant=default]:not(:first-child)]:border-l', '[&>[data-slot=button]:not([data-variant]):not(:first-child)]:border-primary-foreground/20 [&>[data-slot=button]:not([data-variant]):not(:first-child)]:border-l', '[&>[data-slot=button][data-variant=secondary]:not(:first-child)]:border-border [&>[data-slot=button][data-variant=secondary]:not(:first-child)]:border-l', '[&>[data-slot=button][data-variant=destructive]:not(:first-child)]:border-l [&>[data-slot=button][data-variant=destructive]:not(:first-child)]:border-white/20', ], orientation === 'vertical' && [ '[&>[data-slot=button]]:rounded-none [&>button]:rounded-none', '[&>.contents>button]:rounded-none', '[&>[data-slot=button]:first-child]:rounded-t-md [&>button:first-child]:rounded-t-md', '[&>.contents:first-child>button]:rounded-t-md', '[&>[data-slot=button]:last-child]:rounded-b-md [&>button:last-child]:rounded-b-md', '[&>.contents:last-child>button]:rounded-b-md', '[&>[data-slot=button]:only-child]:rounded-md [&>button:only-child]:rounded-md', '[&>.contents:only-child>button]:rounded-md', '[&>[data-slot=button]:not(:first-child)]:-mt-px [&>button:not(:first-child)]:-mt-px', '[&>.contents:not(:first-child)>button]:-mt-px', '[&>[data-slot=button][data-variant=default]:not(:first-child)]:border-primary-foreground/20 [&>[data-slot=button][data-variant=default]:not(:first-child)]:border-t', '[&>[data-slot=button]:not([data-variant]):not(:first-child)]:border-primary-foreground/20 [&>[data-slot=button]:not([data-variant]):not(:first-child)]:border-t', '[&>[data-slot=button][data-variant=secondary]:not(:first-child)]:border-border [&>[data-slot=button][data-variant=secondary]:not(:first-child)]:border-t', '[&>[data-slot=button][data-variant=destructive]:not(:first-child)]:border-t [&>[data-slot=button][data-variant=destructive]:not(:first-child)]:border-white/20', ], ], !attached && (orientation === 'vertical' ? 'gap-1' : 'gap-1.5'), this.className, ) } }src/app/components/ui/button/button.variants.ts2.7 kB
import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' /** * Variant definitions live in their own file (rather than the package * `index.ts`) so `Button.vue` can `import { buttonVariants } from * './button.variants'` without creating a circular dependency through the * index. See card.variants.ts for the same pattern + the SSR symptom that * motivated the split. */ export const buttonVariants = cva( "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,background-color,border-color,box-shadow,opacity,transform,scale] duration-150 active:scale-[0.97] active:duration-100 touch-manipulation disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90', destructive: 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', outline: 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', link: 'text-primary underline-offset-4 hover:underline', }, size: { default: 'h-9 px-4 py-2 has-[>svg]:px-3', sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', xs: 'h-7 rounded-md gap-1 px-2 has-[>svg]:px-1.5 text-xs', icon: 'size-9', 'icon-sm': 'size-8', 'icon-lg': 'size-10', // Square counterpart of `xs` (h-7). Dense data toolbars run at 28px and // had no icon-only tier to match, so icon controls sat 4px taller than // the buttons beside them. Icons inside carry their own size class. 'icon-xs': 'size-7', // Row-action tier: the hover-revealed copy control inside a two-line // table cell. Anything taller than the 20px text line it sits on makes // the row grow when the control appears. 'icon-2xs': 'size-5', }, }, defaultVariants: { variant: 'default', size: 'default', }, }, ) export type ButtonVariants = VariantProps<typeof buttonVariants>src/app/components/ui/button/index.ts0.2 kB
export { UiButtonComponent, buttonVariants, type ButtonVariants, type ButtonSize, type ButtonVariant, } from './button.component' export { UiButtonGroupComponent, type ButtonGroupOrientation } from './button-group.component'
Raw manifest:https://uipkge.dev/r/angular/button.json