diff --git a/src/components/button/button.html b/src/components/button/button.html
new file mode 100644
index 00000000000..4e3df0e2266
--- /dev/null
+++ b/src/components/button/button.html
@@ -0,0 +1 @@
+
diff --git a/src/components/button/button.scss b/src/components/button/button.scss
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/components/button/button.spec.ts b/src/components/button/button.spec.ts
new file mode 100644
index 00000000000..44347a7be1d
--- /dev/null
+++ b/src/components/button/button.spec.ts
@@ -0,0 +1,49 @@
+import {
+ it,
+ iit,
+ describe,
+ ddescribe,
+ expect,
+ inject,
+ injectAsync,
+ TestComponentBuilder,
+ beforeEachProviders,
+ beforeEach,
+} from 'angular2/testing';
+import {provide, Component} from 'angular2/core';
+import {MdButton} from './button';
+
+
+describe('MdButton', () => {
+ let builder: TestComponentBuilder;
+
+ beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { builder = tcb; }));
+
+ it('should ...', () => {
+ return builder.createAsync(MdButton).then((fixture) => {
+ fixture.detectChanges();
+ });
+ });
+
+
+
+
+});
+
+
+
+/** Test component that contains an MdButton. */
+@Component({
+ selector: 'test-app',
+ directives: [MdButton],
+ template:
+ ``,
+})
+class TestApp {
+ clickCount: number = 0;
+ isDisabled: boolean = false;
+
+ increment() {
+ this.clickCount++;
+ }
+}
diff --git a/src/components/button/button.ts b/src/components/button/button.ts
new file mode 100644
index 00000000000..2a4a8e04838
--- /dev/null
+++ b/src/components/button/button.ts
@@ -0,0 +1,95 @@
+import {Component, View, ViewEncapsulation, OnChanges, SimpleChange} from 'angular2/core';
+
+
+// TODO(jelbourn): Ink ripples.
+// TODO(jelbourn): Make the `isMouseDown` stuff done with one global listener.
+
+@Component({
+ selector: '[md-button]:not(a), [md-raised-button]:not(a), [md-fab]:not(a)',
+ host: {
+ '(mousedown)': 'onMousedown()',
+ '(focus)': 'onFocus()',
+ '(blur)': 'onBlur()',
+ '[class.md-button-focus]': 'isKeyboardFocused',
+ },
+})
+@View({
+ templateUrl: 'package:angular2_material/src/components/button/button.html',
+ styleUrls: ['package:angular2_material/src/components/button/button.css'],
+ encapsulation: ViewEncapsulation.None,
+})
+export class MdButton {
+ /** Whether a mousedown has occured on this element in the last 100ms. */
+ isMouseDown: boolean = false;
+
+ /** Whether the button has focus from the keyboard (not the mouse). Used for class binding. */
+ isKeyboardFocused: boolean = false;
+
+ onMousedown() {
+ // We only *show* the focus style when focus has come to the button via the keyboard.
+ // The Material Design spec is silent on this topic, and without doing this, the
+ // button continues to look :active after clicking.
+ // @see http://marcysutton.com/button-focus-hell/
+ this.isMouseDown = true;
+ setTimeout(() => {this.isMouseDown = false}, 100);
+ }
+
+ onFocus() {
+ this.isKeyboardFocused = !this.isMouseDown;
+ }
+
+ onBlur() {
+ this.isKeyboardFocused = false;
+ }
+}
+
+
+@Component({
+ selector: 'a[md-button], a[md-raised-button], a[md-fab]',
+ inputs: ['disabled'],
+ host: {
+ '(click)': 'onClick($event)',
+ '(mousedown)': 'onMousedown()',
+ '(focus)': 'onFocus()',
+ '(blur)': 'onBlur()',
+ '[tabIndex]': 'tabIndex',
+ '[class.md-button-focus]': 'isKeyboardFocused',
+ '[attr.aria-disabled]': 'isAriaDisabled',
+ },
+})
+@View({
+ templateUrl: 'package:angular2_material/src/components/button/button.html',
+ encapsulation: ViewEncapsulation.None
+})
+export class MdAnchor extends MdButton implements OnChanges {
+ tabIndex: number;
+ disabled_: boolean;
+
+ get disabled(): boolean {
+ return this.disabled_;
+ }
+
+ set disabled(value) {
+ // The presence of *any* disabled value makes the component disabled, *except* for false.
+ this.disabled_ = value != null && this.disabled !== false;
+ }
+
+ onClick(event: Event) {
+ // A disabled anchor shouldn't navigate anywhere.
+ if (this.disabled) {
+ event.preventDefault();
+ }
+ }
+
+ /** Invoked when a change is detected. */
+ ngOnChanges(changes: {[propName: string]: SimpleChange}) {
+ // A disabled anchor should not be in the tab flow.
+ this.tabIndex = this.disabled ? -1 : 0;
+ }
+
+ /** Gets the aria-disabled value for the component, which must be a string for Dart. */
+ get isAriaDisabled(): string {
+ return this.disabled ? 'true' : 'false';
+ }
+}
+