diff --git a/n-link-core/components/AppButton.spec.ts b/n-link-core/components/AppButton.spec.ts
new file mode 100644
index 0000000..29979e7
--- /dev/null
+++ b/n-link-core/components/AppButton.spec.ts
@@ -0,0 +1,35 @@
+import { describe, it, expect } from 'vitest';
+import { mount } from '@vue/test-utils';
+import AppButton from './AppButton.vue';
+
+describe('AppButton', () => {
+ it('renders slot content', () => {
+ const wrapper = mount(AppButton, { slots: { default: 'Download' } });
+ expect(wrapper.text()).toBe('Download');
+ });
+
+ it('applies the primary accent style by default', () => {
+ const wrapper = mount(AppButton, { slots: { default: 'Go' } });
+ expect(wrapper.get('button').classes()).toContain('bg-accent');
+ });
+
+ it('applies the danger variant', () => {
+ const wrapper = mount(AppButton, {
+ props: { variant: 'danger' }, slots: { default: 'Delete' },
+ });
+ expect(wrapper.get('button').classes()).toContain('bg-danger');
+ });
+
+ it('emits click when enabled', async () => {
+ const wrapper = mount(AppButton, { slots: { default: 'Go' } });
+ await wrapper.get('button').trigger('click');
+ expect(wrapper.emitted('click')).toHaveLength(1);
+ });
+
+ it('reflects the disabled prop', () => {
+ const wrapper = mount(AppButton, {
+ props: { disabled: true }, slots: { default: 'Go' },
+ });
+ expect(wrapper.get('button').attributes('disabled')).toBeDefined();
+ });
+});
diff --git a/n-link-core/components/AppButton.vue b/n-link-core/components/AppButton.vue
new file mode 100644
index 0000000..28ba0e0
--- /dev/null
+++ b/n-link-core/components/AppButton.vue
@@ -0,0 +1,31 @@
+
+
+
+
+