question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Component Proposal

See original GitHub issue

Thank you all for the feedback, questions, ideas and suggestions. I have edited the proposed solution o reflect the discussion. Proposed Draft 2020-13-01 04:50 GMT


Related issues #7, #60

While working on twind/styled (PR #7) i realized that twind does not have a good component composition model. With component composition we mean re-using styles for several components while allowing to override certain styles like changing the background color.

Problem Statement

As a component author I want to re-use tailwind directive styles for defining my component and allow users of the component to override styles using tailwind directive. Additionally I want to be able to extend a component and override or add some styles using tailwind rules.

The problem we try to solve is component based composition while tw should keep the expected tailwind behavior.

One way to do composition is utility combinations to recreate the same component in many different places (see Extracting Components). I would call this class composition as it applies or groups several class names for a component.

Details with an example and its problems
const Button = ({ className, children}) => {
  return <button className={tw`inline-block bg-gray-500 text-base ${className}`}>{children}</button>
}

const ButtonBlock = ({ className, children}) => {
  return <Button className={`block ${className}`}>{children}</Button>
}

<Button>gray-500</Button>
<Button className="bg-red-500 text-lg">red-500 large</Button>

The example above does not reliably work because the injected css classes have all the same specificity and therefore the order they appear in the stylesheet determine which styles are applied.

It is really difficult to know which directive does override another. Lets stick with bg-* but there are others. The bg prefix and its plugin handle several css properties where background-color is only one of them.

This ambiguity makes class based composition really difficult. That was the reason we introduced the override variant.

Consider the following twind/styled (PR #7) example:

const Button = twind.button`
  text(base blue-600)
  rounded-sm
  border(& solid 2 blue-600)
  m-4 py-1 px-4
`

// Create a child component overriding some colors
const PurpleButton = twind(Button)`
  override:(text-purple-600 border-purple-600)
`

As you see it is difficult to override certain utility classes on usage or when creating a child component. For this to work twind introduced the override variant which increases the specificity of the classes it is applied to. But what do you do for a grandchild component or if you want to override the PurpleButton styles? override:override:...? There must be a better way to solve this problem.

tailwind has a component concept using @apply which basically merges the css rules of several tailwind classes into one class. twin.macro does the same.

That is something I would call style composition and is currently not available in twind.

Details of tailwind @apply

Tailwindcss provides @apply to extract component classes which merges the underlying styles of the utility classes into a single css class. That is something i would call style composition and is currently not available in twind.

.btn-indigo {
  @apply py-2 px-4 bg-indigo-500 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-opacity-75;
}

twind.macro does the same during build time to generate css-in-js objects which are evaluated with a runtime like emotion or styled-component:

const hoverStyles = css`
  &:hover {
    border-color: black;
    ${tw`text-black`}
  }
`
const Input = ({ hasHover }) => (
  <input css={[tw`border`, hasHover && hoverStyles]} />
)

The tw function from twin.macro acts like the @apply helper from tailwindcss.

Lets summarize both composition approaches:

  • class: apply several utility classes on an element
  • style: extract utility css declarations and merge them into one css class

Proposed Solution

Recap of available APIs in twind and their transformations:

  • tw: one tailwind rule => one class name – with side effect of inserting the css into the stylesheet
  • css: css rules => one class name (via tw) – lazy evaluated (injected by tw on first use)

When i look at this i see a missing piece:

  • tw.apply: several tailwind rules => one class name (via tw) – lazy evaluated (injected by tw on first use)

    All considers names
    • tw.apply => to mirror tailwindcss @apply
    • css.of => as it create one big css object basically
    • translate => as it translate tailwind rules to an css object
    • compose => as it merges tailwind rules together
const btn = tw.apply`inline-block bg-gray-500 text-base`
// => generates on css class with all declarations of the above rules when used

const btnBlick = tw.apply`${btn} block`
// => generates on css class with all declarations of btn & block
// Never used => never injected

<button class={tw`${btn}`}>gray-500</button>
// => tw-btn

<button class={tw`${btn} bg-red-500 text-lg`}>red-500 large</button>
// => tw-btn bg-red-500 text-lg

That API needs to

  • generate one style object eg one css class combining all tailwind rules by deep merging rules in order of declaration

  • allow utility classes applied on the same element override its styles; eg styles are injected after base (preflight) and before utility classes

  • can be used with tw => tw(tw.apply(...)); eg implement as an inline directive

  • allow to inject the styles and access the class name without calling tw => result.toString() and result.valueOf()

  • support template literal, strings, arrays, objects and other inline directives (incl css) as parameters

    Rule Precedence Calculation

    To have a predictable styling the styles must be ordered.

    This order is represented by a precedence number. The lower values are inserted before higher values. Meaning higher precedence styles overwrite lower precedence styles.

    Each rule has some traits that are put into a bit set which form the precedence:

    bits trait
    1 dark mode
    2 layer: base = 0, components = 1, utilities = 2 , css = 3
    1 screens: is this a responsive variation of a rule
    5 responsive based on min-width
    4 at-rules
    17 pseudo and group variants
    4 number of declarations (descending)
    4 greatest precedence of properties

    Dark Mode: 1 bit

    Flag for dark mode rules.

    Layer: 3 bits

    • base = 0: The preflight styles and any base styles registered by plugins.
    • components = 1: Component classes and any component classes registered by plugins.
    • utilities = 2: Utility classes and any utility classes registered by plugins.
    • css = 3: Inline plugins

    Screens: 1 bit

    Flag for screen variants. They may not always have a min-width to be detected by Responsive below.

    Responsive: 5 bits

    Based on extracted min-width value:

    • 576px -> 3
    • 1536px -> 9
    • 36rem -> 3
    • 96rem -> 9

    At-Rules: 4 bits

    Based on the count of special chars (-:,) within the at-rule.

    Pseudo and group variants: 17 bits

    Ensures predictable order of pseudo classes.

    Number of declarations (descending): 4 bits

    Allows single declaration styles to overwrite styles from multi declaration styles.

    Greatest precedence of properties: 4 bits

    Ensure shorthand properties are inserted before longhand properties; eg longhand override shorthand

  • be lazy evaluated because it may never be used

    Why lazy?

    For one to prevent unnecessary style injection and to prevent problems when importing a component library that uses this API before invoking setup.

Here are some examples using tw.apply to get a feeling for the API:

Basic usage

Please note that the utility classes are always defined after the component styles which allows them to overrides certain styles.

import { tw } from 'twind'

const btn = tw.apply`
  py-2 px-4
  font-semibold
  rounded-lg shadow-md
  focus:(outline-none ring(2 indigo-400 opacity-75))
`

tw`${btn} font-bold`
// => .tw-btn .font-bold
// CSS:
// .tw-XXXX { padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; padding-right: 1rem; font-weight: 600; ...}
// .font-bold { font-weight: 700; }

const btnLarge = tw.apply`${btn} py-4 px-8`
// Result: () => ({ paddingTop: '1rem', paddingBottom: '1rem', paddingLeft: '2rem', paddingRight: '2rem', fontWeight: '600', ... })

tw`${btnLarge} rounded-md`
// => .tw-btn-large .rounded-md
// CSS:
// .tw-btn-large { padding-top: 1rem; padding-bottom: 1rem; padding-left: 2rem; padding-right: 2rem; font-weight: 600; ... }
// .rounded-md { ... }
twin.macro and styled-component compatibility eg generate one class

The would be possible as the returned function has toString and valueOf methods which inject the styles and return the class name:

<button className={tw.apply`bg-red bg-blue`}>blue</button>
// => tw-red-blue

document.body.className = tw.apply`bg-blue bg-red`
// => tw-blue-red

Or use this helper:

// There is a better name out there somewhere
const twind = (...args) => tw(tw.apply(...args))

<button className={twind`bg-red bg-blue`}>blue</button>
// => tw-red-blue

document.body.className = twind`bg-blue bg-red`
// => tw-blue-red
`css` can be used within `tw.apply`
const btn = tw.apply`
  py-2 px-4
  ${css({
    borderColor: 'black',
  })}
`
Using within css – pending

tw.apply can be used with css ( (pending variable arguments, array support):

const prose = css(
  tw.apply`text-gray-700 dark:text-gray-300`,
  {
    p: tw.apply`my-5`,
    h1: tw.apply`text-black dark:text-white`,
  },
  {
    h1: {
      fontWeight: '800',
      fontSize: '2.25em',
      marginTop: '0',
      marginBottom: '0.8888889em',
      lineHeight: '1.1111111',
    }
  }
)

Using template literal syntax (pending, but i’m working on it):

const prose = css`
  ${tw.apply`text-gray-700 dark:text-gray-300`)

  p { ${tw.apply('my-5')} }

  h1 {
    ${tw.apply`text-black dark:text-white`}
    font-weight: 800;
    font-size: 2.25em;
    margin-top: 0;
    margin-bottom: 0.8888889em;
    line-height: 1.1111111;
  }
`
`twind/styled` would then be a small react wrapper around `tw.apply`
const Button = twind.button`
  text(base blue-600)
  rounded-sm
  border(& solid 2 blue-600)
  m-4 py-1 px-4
`

const PurpleButton = twind(Button)`
  text-purple-600 border-purple-600
`
Using tailwind directives with `animation` from `twind/css`
const motion = animation('.6s ease-in-out infinite', {
  '0%': tw.apply`scale-100`,
  '50%': tw.apply`scale-125 rotate-45`,
  '100%': tw.apply`scale-100 rotate-0`,
})
A react button component
import { tw } from 'twind'

const variantMap = {
  success: "green",
  primary: "blue",
  warning: "yellow",
  info: "gray",
  danger: "red"
}

const sizeMap = {
  sm: tw.apply`text-xs py(2 md:1) px-2`,
  md: tw.apply`text-sm py(3 md:2) px-2`,
  lg: tw.apply`text-lg py-2 px-4`,
  xl: tw.apply`text-xl py-3 px-6`
}

const baseStyles = tw.apply`
  w(full md:auto)
  text(sm white uppercase)
  px-4
  border-none
  transition-colors
  duration-300
`

function Button({ size = 'md', variant = "primary", round = false, disabled = false, className, children }) {
  // Collect all styles into one class
  const instanceStyles = tw.apply`
    ${baseStyles}
    bg-${variantMap[variant]}(600 700(hover:& focus:&)))
    ${sizeMap[size]}
    rounded-${round ? "full" : "lg"}
    ${disabled && "bg-gray-400 text-gray-100 cursor-not-allowed"}
  `
  
  // Allow passed classNames to override instance styles
  return <button className={tw(instanceStyles, className)}>{children}</button>
}

render(<Button variant="info" className="text-lg rounded-md">Click me</Button>)

Discared Proposed Solutions

1. Nested tw (https://github.com/tw-in-js/twind/issues/73#issuecomment-758094856)

  • tw by itself behaves as it does now, untouched
  • nested tw has a new behavior
tw`bg-red bg-blue`;
// css .bg-red {}, .bg-blue {} are appended
// result is bg-red bg-blue

const base = tw`bg-red`;
// css .bg-red {} is NOT appended as it already was on line 1
// result is bg-red

tw`${base} bg-blue`;
// css .tw-generated-bg-blue{} is appended
// result is bg-red tw-generated-bg-blue

Open question @43081j: How to ensure that generated-bg-blue has a higher precedence than bg-red?

2. Reverse Translation (https://github.com/tw-in-js/twind/issues/73#issuecomment-758062412)

Enhance tw to detect directives that override previous ones and omit those from the result class names string.

const btn = tw`py-2 px-4 font-semibold`
// => py-2 px-4 font-semibold

tw`${btn} py-4 px-8`
// => font-semibold py-4 px-8

tw`py-4 ${btn} px-8`
// => py-2 font-semibold px-8

Algorithm

  1. transform all rules to their css equivalent
  2. merge all css into one object
  3. for each rule check if their css is contained within the css object; if that is the case include it in the output

Step 1 and 2 are possible. Step 3 may have some edge cases like what to do if the css is a partial match:

.bg-red-500 {
  --tw-bg-opacity: 1;
  background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}

.bg-opacity-5 {
  --tw-bg-opacity: 0.05;
}

bg-opacity-5 partially overrides bg-red-500. Both must be included in the output.

Another edge case may be if the css helper is used. And i’m sure there a some i haven’t identified yet.

3. twind/compose

Introduce compose as a new function which would extract the styles of the provided directives and returns an inline directive with an css style object containing all deep merged rules which can be used with tw. The generated styles would have a lower precedence than the utility classes which would allow to use tailwind directives to override styles.

The following examples use template literals but well known tw arguments like strings, arrays, objects, and inline directives, would be supported.

import { compose } from 'twind/compose'

const btn = compose`
  py-2 px-4
  font-semibold
  rounded-lg shadow-md
  focus:(outline-none ring(2 indigo-400 opacity-75))
`
// Result: () => ({ paddingTop: '0.5rem', paddingBottom: '0.5rem', paddingLeft: '1rem', paddingRight: '1rem', fontWeight: '600', ... })
// CSS:
// .tw-XXXX { padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; padding-right: 1rem; font-weight: 600; ...}

const btnLarge = compose`${btn} py-4 px-8`
// Result: () => ({ paddingTop: '1rem', paddingBottom: '1rem', paddingLeft: '2rem', paddingRight: '2rem', fontWeight: '600', ... })
// CSS:
// .tw-YYYY { padding-top: 1rem; padding-bottom: 1rem; padding-left: 2rem; padding-right: 2rem; font-weight: 600; ... }

tw`${btnLarge} rounded-md`
// => .tw-btn .tw-btn-large .rounded-md

css can be used within compose:

const btn = compose`
  py-2 px-4
  ${css({
    borderColor: 'black',
  })}
`
Using within css – pending (Click to expand)

compose can be used with css ( (pending variable arguments, array support):

const prose = css(
  compose`text-gray-700 dark:text-gray-300`,
  {
    p: compose`my-5`,
    h1: compose`text-black dark:text-white`,
  },
  {
    h1: {
      fontWeight: '800',
      fontSize: '2.25em',
      marginTop: '0',
      marginBottom: '0.8888889em',
      lineHeight: '1.1111111',
    }
  }
)

Using template literal syntax (pending, but i’m working on it):

const prose = css`
  ${compose`text-gray-700 dark:text-gray-300`)

  p { ${compose('my-5')} }

  h1 {
    ${compose`text-black dark:text-white`}
    font-weight: 800;
    font-size: 2.25em;
    margin-top: 0;
    margin-bottom: 0.8888889em;
    line-height: 1.1111111;
  }
`

twind/styled would then be a small react wrapper around the base compose:

const Button = twind.button`
  text(base blue-600)
  rounded-sm
  border(& solid 2 blue-600)
  m-4 py-1 px-4
`

const PurpleButton = twind(Button)`
  text-purple-600 border-purple-600
`
Show more examples (click to expand)

Using tailwind directives with animation from twind/css:

const motion = animation('.6s ease-in-out infinite', {
  '0%': compose`scale-100`,
  '50%': compose`scale-125 rotate-45`,
  '100%': compose`scale-100 rotate-0`,
})

Here is an example for an react button component:

import { tw } from 'twind'
import { compose } from 'twind/compose'

const variantMap = {
  success: "green",
  primary: "blue",
  warning: "yellow",
  info: "gray",
  danger: "red"
}

const sizeMap = {
  sm: compose`text-xs py(2 md:1) px-2`,
  md: compose`text-sm py(3 md:2) px-2`,
  lg: compose`text-lg py-2 px-4`,
  xl: compose`text-xl py-3 px-6`
}

const baseStyles = compose`
  w(full md:auto)
  text(sm white uppercase)
  px-4
  border-none
  transition-colors
  duration-300
`

function Button({ size = 'md', variant = "primary", round = false, disabled = false, className, children }) {
  const instanceStyles = compose`
    ${baseStyles}
    bg-${variantMap[variant]}(600 700(hover:& focus:&)))
    ${sizeMap[size]}
    rounded-${round ? "full" : "lg"}
    ${disabled && "bg-gray-400 text-gray-100 cursor-not-allowed"}
  `
  
  // Allow passed classNames to override instance styles
  return <button className={tw(instanceStyles, className)}>{children}</button>
}

render(<Button variant="info" className="text-lg rounded-md">Click me</Button>)

4. Enhance twind/css

Extend twind/css to extract the styles of the provided directives and return an inline directive with an css style object containing all deep merged rules which can be used with tw. The generated styles would have a lower precedence than the utility classes which would allow to use tailwind directives to override styles.

css currently accepts an css object. We could extend it to accept strings which are directives:

css would now be a translator from tailwind rules to css object.

Please note that the template literal syntax may come with issues in editors and prettier as it may be mistaken for real css. If anyone has a solution please comment below.

const btn = css('py-2 px-4 font-semibold')
// => { padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; padding-right: 1rem; font-weight: 600; }

tw`${btn} py-4 px-8`
// => tw-xxx py-4 px-8

const largeBtn = css`${btn} py-4 px-8`
// => { padding-top: 1rem; padding-bottom: 1rem; padding-left: 2rem; padding-right: 2rem; font-weight: 600; }
tw`${largeBtn} font-bold`
// => tw-yyyy font-bold
Show more examples (click to expand)

Using tailwind directives with animation from twind/css:

import { css, animation } from 'twind/css'

const motion = animation('.6s ease-in-out infinite', {
  '0%': css('scale-100'),
  '50%': css('scale-125 rotate-45'),
  '100%': css('scale-100 rotate-0'),
})

Here is an example for an react button component:

import { tw } from 'twind'
import { css } from 'twind/css'

const variantMap = {
  success: "green",
  primary: "blue",
  warning: "yellow",
  info: "gray",
  danger: "red"
}

const sizeMap = {
  sm: css('text-xs py(2 md:1) px-2'),
  md: css('text-sm py(3 md:2) px-2'),
  lg: css('text-lg py-2 px-4'),
  xl: css('text-xl py-3 px-6')
}

const baseStyles = css(`
  w(full md:auto)
  text(sm white uppercase)
  px-4
  border-none
  transition-colors
  duration-300
`)

function Button({ size = 'md', variant = "primary", round = false, disabled = false, className, children }) {
  const instanceStyles = css(`
    ${baseStyles}
    bg-${variantMap[variant]}(600 700(hover:& focus:&)))
    ${sizeMap[size]}
    rounded-${round ? "full" : "lg"}
    ${disabled && "bg-gray-400 text-gray-100 cursor-not-allowed"}
  `)
  
  // Allow passed classNames to override instance styles
  return <button className={tw(instanceStyles, className)}>{children}</button>
}

render(<Button variant="info" className="text-lg rounded-md">Click me</Button>)

Summary

I hope have summarized all sides of the discussion and everybody sees theirs points reflected in the proposed solution.


Thank you for reading this whole thing ❤️

/cc @tw-in-js/contributors

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:4
  • Comments:47 (1 by maintainers)

github_iconTop GitHub Comments

7reactions
sastancommented, Jan 13, 2021

It would be great if you could give me a 👍 if you are happy with the proposed solution. Please leave a comment if you are not.

/cc @tw-in-js/contributors

7reactions
sastancommented, Jan 12, 2021

Here are some examples using tw.apply to get a feeling for the API:

Basic usage

Please note that the utility classes are always defined after the component styles which allows them to overrides certain styles.

import { tw } from 'twind'

const btn = tw.apply`
  py-2 px-4
  font-semibold
  rounded-lg shadow-md
  focus:(outline-none ring(2 indigo-400 opacity-75))
`

tw`${btn} font-bold`
// => .tw-btn .font-bold
// CSS:
// .tw-XXXX { padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; padding-right: 1rem; font-weight: 600; ...}
// .font-bold { font-weight: 700; }

const btnLarge = tw.apply`${btn} py-4 px-8`
// Result: () => ({ paddingTop: '1rem', paddingBottom: '1rem', paddingLeft: '2rem', paddingRight: '2rem', fontWeight: '600', ... })

tw`${btnLarge} rounded-md`
// => .tw-btn-large .rounded-md
// CSS:
// .tw-btn-large { padding-top: 1rem; padding-bottom: 1rem; padding-left: 2rem; padding-right: 2rem; font-weight: 600; ... }
// .rounded-md { ... }
twin.macro and styled-component compatibility eg generate one class

The would be possible as the returned function has toString and valueOf methods which inject the styles and return the class name:

<button className={tw.apply`bg-red bg-blue`}>blue</button>
// => tw-red-blue

document.body.className = tw.apply`bg-blue bg-red`
// => tw-blue-red

Or use this helper:

// There is a better name out there somewhere
const twind = (...args) => tw(tw.apply(...args))

<button className={twind`bg-red bg-blue`}>blue</button>
// => tw-red-blue

document.body.className = twind`bg-blue bg-red`
// => tw-blue-red
`css` can be used within `tw.apply`
const btn = tw.apply`
  py-2 px-4
  ${css({
    borderColor: 'black',
  })}
`
Using within css – pending

tw.apply can be used with css ( (pending variable arguments, array support):

const prose = css(
  tw.apply`text-gray-700 dark:text-gray-300`,
  {
    p: tw.apply`my-5`,
    h1: tw.apply`text-black dark:text-white`,
  },
  {
    h1: {
      fontWeight: '800',
      fontSize: '2.25em',
      marginTop: '0',
      marginBottom: '0.8888889em',
      lineHeight: '1.1111111',
    }
  }
)

Using template literal syntax (pending, but i’m working on it):

const prose = css`
  ${tw.apply`text-gray-700 dark:text-gray-300`)

  p { ${tw.apply('my-5')} }

  h1 {
    ${tw.apply`text-black dark:text-white`}
    font-weight: 800;
    font-size: 2.25em;
    margin-top: 0;
    margin-bottom: 0.8888889em;
    line-height: 1.1111111;
  }
`
`twind/styled` would then be a small react wrapper around `tw.apply`
const Button = twind.button`
  text(base blue-600)
  rounded-sm
  border(& solid 2 blue-600)
  m-4 py-1 px-4
`

const PurpleButton = twind(Button)`
  text-purple-600 border-purple-600
`
Using tailwind directives with `animation` from `twind/css`
const motion = animation('.6s ease-in-out infinite', {
  '0%': tw.apply`scale-100`,
  '50%': tw.apply`scale-125 rotate-45`,
  '100%': tw.apply`scale-100 rotate-0`,
})
A react button component
import { tw } from 'twind'

const variantMap = {
  success: "green",
  primary: "blue",
  warning: "yellow",
  info: "gray",
  danger: "red"
}

const sizeMap = {
  sm: tw.apply`text-xs py(2 md:1) px-2`,
  md: tw.apply`text-sm py(3 md:2) px-2`,
  lg: tw.apply`text-lg py-2 px-4`,
  xl: tw.apply`text-xl py-3 px-6`
}

const baseStyles = tw.apply`
  w(full md:auto)
  text(sm white uppercase)
  px-4
  border-none
  transition-colors
  duration-300
`

function Button({ size = 'md', variant = "primary", round = false, disabled = false, className, children }) {
  // Collect all styles into one class
  const instanceStyles = tw.apply`
    ${baseStyles}
    bg-${variantMap[variant]}(600 700(hover:& focus:&)))
    ${sizeMap[size]}
    rounded-${round ? "full" : "lg"}
    ${disabled && "bg-gray-400 text-gray-100 cursor-not-allowed"}
  `
  
  // Allow passed classNames to override instance styles
  return <button className={tw(instanceStyles, className)}>{children}</button>
}

render(<Button variant="info" className="text-lg rounded-md">Click me</Button>)
Read more comments on GitHub >

github_iconTop Results From Across the Web

Basic components of a proposal | Foundation Relations
Basic components of a proposal · 1. Abstract/Summary · 2. Statement of Need · 3. Project Activity, Methodology and Outcomes · 4. Evaluation...
Read more >
Key Elements of a Complete Proposal
Key Elements of a Complete Proposal. Most proposals contain the elements described here. If the prospective grantor prescribes a format, follow it!
Read more >
Components of a Proposal | It's Your Yale
What should be included in a proposal? ; Title Page, Includes the title and duration of the project, amount requested, name and address...
Read more >
Basic Components of a Proposal
Basic components of a proposal include the title page, abstract summary, statement of work, budget, budget justification, biographical sketch (CV), ...
Read more >
Proposal Components | DoResearch
A proposal can have various components depending on the sponsor and solicitation requirements. A solicitation is also known as a call for ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found